diff options
467 files changed, 9993 insertions, 8151 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 705a4df6c30f..efd85788574e 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -430,10 +430,7 @@ java_aconfig_library { aconfig_declarations { name: "com.android.media.flags.bettertogether-aconfig", package: "com.android.media.flags", - srcs: [ - "media/java/android/media/flags/media_better_together.aconfig", - "media/java/android/media/flags/fade_manager_configuration.aconfig", - ], + srcs: ["media/java/android/media/flags/media_better_together.aconfig"], } java_aconfig_library { diff --git a/Ravenwood.bp b/Ravenwood.bp index 03f3f0f3d61d..f330ad14ea57 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -65,7 +65,7 @@ java_genrule { // depend on it. java_genrule { name: "framework-minus-apex.ravenwood", - defaults: ["hoststubgen-for-prototype-only-genrule"], + defaults: ["ravenwood-internal-only-visibility-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-minus-apex.ravenwood-base{ravenwood.jar}", diff --git a/apct-tests/perftests/healthconnect/Android.bp b/apct-tests/perftests/healthconnect/Android.bp new file mode 100644 index 000000000000..c2d0a6f200a0 --- /dev/null +++ b/apct-tests/perftests/healthconnect/Android.bp @@ -0,0 +1,44 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "HealthConnectPerfTests", + + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + + static_libs: [ + "androidx.test.rules", + "androidx.test.ext.junit", + "apct-perftests-utils", + "collector-device-lib-platform", + ], + + libs: ["android.test.base"], + platform_apis: true, + test_suites: ["device-tests"], + data: [":perfetto_artifacts"], + certificate: "platform", +} diff --git a/apct-tests/perftests/healthconnect/AndroidManifest.xml b/apct-tests/perftests/healthconnect/AndroidManifest.xml new file mode 100644 index 000000000000..6a6370b6214a --- /dev/null +++ b/apct-tests/perftests/healthconnect/AndroidManifest.xml @@ -0,0 +1,26 @@ +<?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 xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.perftests.healthconnect"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.perftests.healthconnect"/> +</manifest> diff --git a/apct-tests/perftests/healthconnect/AndroidTest.xml b/apct-tests/perftests/healthconnect/AndroidTest.xml new file mode 100644 index 000000000000..5036202a9f4a --- /dev/null +++ b/apct-tests/perftests/healthconnect/AndroidTest.xml @@ -0,0 +1,61 @@ +<?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. +--> +<configuration description="Runs HealthConnectPerfTests metric instrumentation."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-metric-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="HealthConnectPerfTests.apk" /> + </target_preparer> + + <!-- Needed for pushing the trace config file --> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" /> + </target_preparer> + + <!-- Needed for pulling the collected trace config on to the host --> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="pull-pattern-keys" value="perfetto_file_path" /> + </metrics_collector> + + <!-- Needed for storing the perfetto trace files in the sdcard/test_results--> + <option name="isolated-storage" value="false" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.perftests.healthconnect" /> + <option name="hidden-api-checks" value="false"/> + + <!-- Listener related args for collecting the traces and waiting for the device to stabilize. --> + <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" /> + <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. --> + <option name="instrumentation-arg" key="newRunListenerMode" value="true" /> + + <!-- ProcLoadListener related arguments --> + <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run --> + <option name="instrumentation-arg" key="procload-collector:per_run" value="true" /> + <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" /> + <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" /> + <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" /> + + <!-- PerfettoListener related arguments --> + <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" /> + <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" /> + + <option name="instrumentation-arg" key="newRunListenerMode" value="true" /> + </test> +</configuration> diff --git a/apct-tests/perftests/healthconnect/OWNERS b/apct-tests/perftests/healthconnect/OWNERS new file mode 100644 index 000000000000..da0b46adeaef --- /dev/null +++ b/apct-tests/perftests/healthconnect/OWNERS @@ -0,0 +1,6 @@ +# Bug component: 1219472 + +arkivanov@google.com +jstembridge@google.com +pratyushmore@google.com +itsleo@google.com diff --git a/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt new file mode 100644 index 000000000000..21a4ca048f62 --- /dev/null +++ b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt @@ -0,0 +1,54 @@ +/* + * 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.perftests.healthconnect + +import android.health.connect.HealthConnectManager +import android.os.SystemClock +import android.perftests.utils.PerfStatusReporter +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Read/write benchmark tests for [HealthConnectManager] + * + * Build/Install/Run: atest HealthConnectReadWritePerfTest + */ +@RunWith(AndroidJUnit4::class) +class HealthConnectReadWritePerfTest { + + @get:Rule + val perfStatusReporter = PerfStatusReporter() + + private val context by lazy { InstrumentationRegistry.getInstrumentation().context } + + private val manager by lazy { + requireNotNull(context.getSystemService(HealthConnectManager::class.java)) + } + + /** + * A first empty test just to setup the test package and make sure it runs properly. + */ + @Test + fun placeholder() { + val state = perfStatusReporter.benchmarkState + while (state.keepRunning()) { + SystemClock.sleep(100) + } + } +}
\ No newline at end of file diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java index 913a76a65026..4d4e3407a3c3 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java @@ -591,6 +591,16 @@ public class AppIdleHistory { if (idle) { newBucket = IDLE_BUCKET_CUTOFF; reason = REASON_MAIN_FORCED_BY_USER; + final AppUsageHistory appHistory = getAppUsageHistory(packageName, userId, + elapsedRealtime); + // Wipe all expiry times that could raise the bucket on reevaluation. + if (appHistory.bucketExpiryTimesMs != null) { + for (int i = appHistory.bucketExpiryTimesMs.size() - 1; i >= 0; --i) { + if (appHistory.bucketExpiryTimesMs.keyAt(i) < newBucket) { + appHistory.bucketExpiryTimesMs.removeAt(i); + } + } + } } else { newBucket = STANDBY_BUCKET_ACTIVE; // This is to pretend that the app was just used, don't freeze the state anymore. diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index bd5c00600044..e589c214801a 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -59,6 +59,7 @@ import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.usage.AppIdleHistory.STANDBY_BUCKET_UNKNOWN; import android.annotation.CurrentTimeMillisLong; +import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -2146,6 +2147,15 @@ public class AppStandbyController } } + /** + * Flush the handler. + * Returns true if successfully flushed within the timeout, otherwise return false. + */ + @VisibleForTesting + boolean flushHandler(@DurationMillisLong long timeoutMillis) { + return mHandler.runWithScissors(() -> {}, timeoutMillis); + } + @Override public void flushToDisk() { synchronized (mAppIdleLock) { diff --git a/api/Android.bp b/api/Android.bp index 9029d252b470..363197a54fac 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -307,10 +307,6 @@ stubs_defaults { "framework-protos", ], flags: [ - "--api-lint-ignore-prefix android.icu.", - "--api-lint-ignore-prefix java.", - "--api-lint-ignore-prefix junit.", - "--api-lint-ignore-prefix org.", "--error NoSettingsProvider", "--error UnhiddenSystemApi", "--error UnflaggedApi", diff --git a/core/api/current.txt b/core/api/current.txt index d3ed89b347ec..ace00fcb58aa 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -43277,6 +43277,7 @@ package android.telephony { field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array"; field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int"; field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array"; + field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY = "cellular_service_capabilities_int_array"; field public static final String KEY_CELLULAR_USAGE_SETTING_INT = "cellular_usage_setting_int"; field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool"; field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool"; @@ -43403,6 +43404,7 @@ package android.telephony { field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT = "parameters_used_for_ntn_lte_signal_bar_int"; field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool"; field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; + field @FlaggedApi("com.android.internal.telephony.flags.hide_prefer_3g_item") public static final String KEY_PREFER_3G_VISIBILITY_BOOL = "prefer_3g_visibility_bool"; field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_daily_notification_count_int"; field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_monthly_notification_count_int"; field public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG = "premium_capability_network_setup_time_millis_long"; @@ -45271,6 +45273,7 @@ package android.telephony { method @Nullable public String getMncString(); method @Deprecated public String getNumber(); method public int getPortIndex(); + method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") @NonNull public java.util.Set<java.lang.Integer> getServiceCapabilities(); method public int getSimSlotIndex(); method public int getSubscriptionId(); method public int getSubscriptionType(); @@ -45352,6 +45355,9 @@ package android.telephony { field public static final int PHONE_NUMBER_SOURCE_CARRIER = 2; // 0x2 field public static final int PHONE_NUMBER_SOURCE_IMS = 3; // 0x3 field public static final int PHONE_NUMBER_SOURCE_UICC = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_DATA = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_SMS = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_VOICE = 1; // 0x1 field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0 field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1 field public static final int USAGE_SETTING_DATA_CENTRIC = 2; // 0x2 @@ -45600,6 +45606,8 @@ package android.telephony { method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabled(); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabledForReason(int); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataRoamingEnabled(); + method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public boolean isDeviceSmsCapable(); + method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public boolean isDeviceVoiceCapable(); method public boolean isEmergencyNumber(@NonNull String); method public boolean isHearingAidCompatibilitySupported(); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isManualNetworkSelectionAllowed(); @@ -45609,9 +45617,9 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE) public boolean isPremiumCapabilityAvailableForPurchase(int); method public boolean isRadioInterfaceCapabilitySupported(@NonNull String); method public boolean isRttSupported(); - method public boolean isSmsCapable(); + method @Deprecated public boolean isSmsCapable(); method @Deprecated public boolean isTtyModeSupported(); - method public boolean isVoiceCapable(); + method @Deprecated public boolean isVoiceCapable(); method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle); method public boolean isWorldPhone(); method @Deprecated public void listen(android.telephony.PhoneStateListener, int); @@ -53524,6 +53532,7 @@ package android.view { method public android.transition.Transition getExitTransition(); method protected final int getFeatures(); method protected final int getForcedWindowFlags(); + method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public boolean getFrameRateBoostOnTouchEnabled(); method @Nullable public android.view.WindowInsetsController getInsetsController(); method @NonNull public abstract android.view.LayoutInflater getLayoutInflater(); method protected final int getLocalFeatures(); @@ -53601,6 +53610,7 @@ package android.view { method public abstract void setFeatureInt(int, int); method public void setFlags(int, int); method public void setFormat(int); + method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setFrameRateBoostOnTouchEnabled(boolean); method public void setGravity(int); method @RequiresPermission(android.Manifest.permission.HIDE_OVERLAY_WINDOWS) public final void setHideOverlayWindows(boolean); method public void setIcon(@DrawableRes int); @@ -53945,6 +53955,7 @@ package android.view { method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom(); method public int getFitInsetsSides(); method public int getFitInsetsTypes(); + method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public boolean getFrameRateBoostOnTouchEnabled(); method public final CharSequence getTitle(); method public boolean isFitInsetsIgnoringVisibility(); method public boolean isHdrConversionEnabled(); @@ -53956,6 +53967,7 @@ package android.view { method public void setFitInsetsIgnoringVisibility(boolean); method public void setFitInsetsSides(int); method public void setFitInsetsTypes(int); + method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setFrameRateBoostOnTouchEnabled(boolean); method public void setHdrConversionEnabled(boolean); method public final void setTitle(CharSequence); method public void setWallpaperTouchEventsEnabled(boolean); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 8ce3a8dfb5a2..51e61e6239bb 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3350,7 +3350,7 @@ package android.companion.virtual.camera { } @FlaggedApi("android.companion.virtual.flags.virtual_camera") public interface VirtualCameraCallback { - method public void onProcessCaptureRequest(int, long, @Nullable android.companion.virtual.camera.VirtualCameraMetadata); + method public default void onProcessCaptureRequest(int, long); method public void onStreamClosed(int); method public void onStreamConfigured(int, @NonNull android.view.Surface, @NonNull android.companion.virtual.camera.VirtualCameraStreamConfig); } @@ -3371,12 +3371,6 @@ package android.companion.virtual.camera { method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback); } - @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraMetadata implements android.os.Parcelable { - method public int describeContents(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraMetadata> CREATOR; - } - @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraStreamConfig implements android.os.Parcelable { ctor public VirtualCameraStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int); method public int describeContents(); @@ -6478,6 +6472,7 @@ package android.media { method public void clearAudioServerStateCallback(); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); + method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int dispatchAudioFocusChangeWithFade(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy, @NonNull java.util.List<android.media.AudioFocusInfo>, @Nullable android.media.FadeManagerConfiguration); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getActiveAssistantServicesUids(); method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getAssistantServicesUids(); @@ -6677,6 +6672,69 @@ package android.media { field public static final int CONTENT_ID_NONE = 0; // 0x0 } + @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") public final class FadeManagerConfiguration implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<android.media.AudioAttributes> getAudioAttributesWithVolumeShaperConfigs(); + method public long getFadeInDelayForOffenders(); + method public long getFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes); + method public long getFadeInDurationForUsage(int); + method @Nullable public android.media.VolumeShaper.Configuration getFadeInVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes); + method @Nullable public android.media.VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(int); + method public long getFadeOutDurationForAudioAttributes(@NonNull android.media.AudioAttributes); + method public long getFadeOutDurationForUsage(int); + method @Nullable public android.media.VolumeShaper.Configuration getFadeOutVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes); + method @Nullable public android.media.VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(int); + method public int getFadeState(); + method @NonNull public java.util.List<java.lang.Integer> getFadeableUsages(); + method @NonNull public java.util.List<android.media.AudioAttributes> getUnfadeableAudioAttributes(); + method @NonNull public java.util.List<java.lang.Integer> getUnfadeableContentTypes(); + method @NonNull public java.util.List<java.lang.Integer> getUnfadeablePlayerTypes(); + method @NonNull public java.util.List<java.lang.Integer> getUnfadeableUids(); + method public boolean isAudioAttributesUnfadeable(@NonNull android.media.AudioAttributes); + method public boolean isContentTypeUnfadeable(int); + method public boolean isFadeEnabled(); + method public boolean isPlayerTypeUnfadeable(int); + method public boolean isUidUnfadeable(int); + method public boolean isUsageFadeable(int); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.media.FadeManagerConfiguration> CREATOR; + field public static final long DURATION_NOT_SET = 0L; // 0x0L + field public static final int FADE_STATE_DISABLED = 0; // 0x0 + field public static final int FADE_STATE_ENABLED_AUTO = 2; // 0x2 + field public static final int FADE_STATE_ENABLED_DEFAULT = 1; // 0x1 + field public static final String TAG = "FadeManagerConfiguration"; + field public static final int VOLUME_SHAPER_SYSTEM_FADE_ID = 2; // 0x2 + } + + public static final class FadeManagerConfiguration.Builder { + ctor public FadeManagerConfiguration.Builder(); + ctor public FadeManagerConfiguration.Builder(long, long); + ctor public FadeManagerConfiguration.Builder(@NonNull android.media.FadeManagerConfiguration); + method @NonNull public android.media.FadeManagerConfiguration.Builder addFadeableUsage(int); + method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes); + method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableContentType(int); + method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableUid(int); + method @NonNull public android.media.FadeManagerConfiguration build(); + method @NonNull public android.media.FadeManagerConfiguration.Builder clearFadeableUsage(int); + method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes); + method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableContentType(int); + method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableUid(int); + method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDelayForOffenders(long); + method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long); + method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForUsage(int, long); + method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes, @Nullable android.media.VolumeShaper.Configuration); + method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInVolumeShaperConfigForUsage(int, @Nullable android.media.VolumeShaper.Configuration); + method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long); + method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutDurationForUsage(int, long); + method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes, @Nullable android.media.VolumeShaper.Configuration); + method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutVolumeShaperConfigForUsage(int, @Nullable android.media.VolumeShaper.Configuration); + method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeState(int); + method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeableUsages(@NonNull java.util.List<java.lang.Integer>); + method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableAudioAttributes(@NonNull java.util.List<android.media.AudioAttributes>); + method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableContentTypes(@NonNull java.util.List<java.lang.Integer>); + method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableUids(@NonNull java.util.List<java.lang.Integer>); + } + public class HwAudioSource { method public boolean isPlaying(); method public void start(); @@ -6895,15 +6953,18 @@ package android.media.audiopolicy { public class AudioPolicy { method public int attachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>); + method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int clearFadeManagerConfigurationForFocusLoss(); method public android.media.AudioRecord createAudioRecordSink(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException; method public android.media.AudioTrack createAudioTrackSource(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException; method public int detachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>); + method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public android.media.FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss(); method public int getFocusDuckingBehavior(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioFocusInfo> getFocusStack(); method public int getStatus(); method public boolean removeUidDeviceAffinity(int); method public boolean removeUserIdDeviceAffinity(int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean sendFocusLoss(@NonNull android.media.AudioFocusInfo) throws java.lang.IllegalStateException; + method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int setFadeManagerConfigurationForFocusLoss(@NonNull android.media.FadeManagerConfiguration); method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void setRegistration(String); method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>); diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index ea9bb396f83c..37692d363a4b 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -537,8 +537,8 @@ public abstract class ActivityManagerInternal { /** * Returns whether the given user requires credential entry at this time. This is used to - * intercept activity launches for locked work apps due to work challenge being triggered or - * when the profile user is yet to be unlocked. + * intercept activity launches for apps corresponding to locked profiles due to separate + * challenge being triggered or when the profile user is yet to be unlocked. */ public abstract boolean shouldConfirmCredentials(@UserIdInt int userId); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index a510c7704751..476232cb40b3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -7733,11 +7733,12 @@ public class Notification implements Parcelable } else if (mPictureIcon.getType() == Icon.TYPE_BITMAP) { // If the icon contains a bitmap, use the old extra so that listeners which look // for that extra can still find the picture. Don't include the new extra in - // that case, to avoid duplicating data. + // that case, to avoid duplicating data. Leave the unused extra set to null to avoid + // crashing apps that came to expect it to be present but null. extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap()); - extras.remove(EXTRA_PICTURE_ICON); + extras.putParcelable(EXTRA_PICTURE_ICON, null); } else { - extras.remove(EXTRA_PICTURE); + extras.putParcelable(EXTRA_PICTURE, null); extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon); } } diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 772b0b46d3b4..47d19eda0c72 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -88,6 +88,10 @@ per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/serve # Pinner per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS +# BackgroundInstallControlManager +per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/OWNERS +per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/OWNERS + # ResourcesManager per-file ResourcesManager.java = file:RESOURCES_OWNERS diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index b11840e8a931..879656a89233 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -220,6 +220,12 @@ public final class CompanionDeviceManager { */ public static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN /** + * Test message type without a response. + * + * @hide + */ + public static final int MESSAGE_ONEWAY_PING = 0x43807378; // +PIN + /** * Message header assigned to the remote authentication handshakes. * * @hide @@ -237,6 +243,18 @@ public final class CompanionDeviceManager { * @hide */ public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES + /** + * Message header assigned to the one-way message sent from the wearable device. + * + * @hide + */ + public static final int MESSAGE_ONEWAY_FROM_WEARABLE = 0x43708287; // +FRW + /** + * Message header assigned to the one-way message sent to the wearable device. + * + * @hide + */ + public static final int MESSAGE_ONEWAY_TO_WEARABLE = 0x43847987; // +TOW /** * The length limit of Association tag. diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl index fac44b50024f..44942d67d489 100644 --- a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl +++ b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright 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. @@ -16,11 +16,11 @@ package android.companion.virtual.camera; import android.companion.virtual.camera.VirtualCameraStreamConfig; -import android.companion.virtual.camera.VirtualCameraMetadata; import android.view.Surface; /** - * Interface for the virtual camera service and system server to talk back to the virtual camera owner. + * Interface for the virtual camera service and system server to talk back to the virtual camera + * owner. * * @hide */ @@ -40,8 +40,7 @@ interface IVirtualCameraCallback { in VirtualCameraStreamConfig streamConfig); /** - * The client application is requesting a camera frame for the given streamId with the provided - * metadata. + * The client application is requesting a camera frame for the given streamId and frameId. * * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to * this stream that was provided during the {@link #onStreamConfigured(int, Surface, @@ -52,16 +51,14 @@ interface IVirtualCameraCallback { * VirtualCameraStreamConfig)} * @param frameId The frameId that is being requested. Each request will have a different * frameId, that will be increasing for each call with a particular streamId. - * @param metadata The metadata requested for the frame. The virtual camera should do its best - * to honor the requested metadata. */ - oneway void onProcessCaptureRequest( - int streamId, long frameId, in VirtualCameraMetadata metadata); + oneway void onProcessCaptureRequest(int streamId, long frameId); /** * The stream previously configured when {@link #onStreamConfigured(int, Surface, * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be - * freed. The Surface was disposed on the client side and should not be used anymore by the virtual camera owner + * freed. The Surface was disposed on the client side and should not be used anymore by the + * virtual camera owner. * * @param streamId The id of the stream that was closed. */ diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java index a18ae03555e9..5b658b883217 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java +++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright 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. @@ -18,7 +18,6 @@ package android.companion.virtual.camera; import android.annotation.FlaggedApi; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SystemApi; import android.companion.virtual.flags.Flags; import android.view.Surface; @@ -50,8 +49,7 @@ public interface VirtualCameraCallback { @NonNull VirtualCameraStreamConfig streamConfig); /** - * The client application is requesting a camera frame for the given streamId with the provided - * metadata. + * The client application is requesting a camera frame for the given streamId and frameId. * * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to * this stream that was provided during the {@link #onStreamConfigured(int, Surface, @@ -62,12 +60,8 @@ public interface VirtualCameraCallback { * VirtualCameraStreamConfig)} * @param frameId The frameId that is being requested. Each request will have a different * frameId, that will be increasing for each call with a particular streamId. - * @param metadata The metadata requested for the frame. The virtual camera should do its best - * to honor the requested metadata but the consumer won't be informed about the metadata set - * for a particular frame. If null, the requested frame can be anything the producer sends. */ - void onProcessCaptureRequest( - int streamId, long frameId, @Nullable VirtualCameraMetadata metadata); + default void onProcessCaptureRequest(int streamId, long frameId) {} /** * The stream previously configured when {@link #onStreamConfigured(int, Surface, diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java index f1eb240301e0..a9392518d0c6 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java +++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright 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. @@ -224,9 +224,8 @@ public final class VirtualCameraConfig implements Parcelable { } @Override - public void onProcessCaptureRequest( - int streamId, long frameId, VirtualCameraMetadata metadata) { - mExecutor.execute(() -> mCallback.onProcessCaptureRequest(streamId, frameId, metadata)); + public void onProcessCaptureRequest(int streamId, long frameId) { + mExecutor.execute(() -> mCallback.onProcessCaptureRequest(streamId, frameId)); } @Override diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java deleted file mode 100644 index 1ba36d08cbeb..000000000000 --- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java +++ /dev/null @@ -1,61 +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 android.companion.virtual.camera; - -import android.annotation.FlaggedApi; -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.companion.virtual.flags.Flags; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Data structure used to store camera metadata compatible with VirtualCamera. - * - * @hide - */ -@SystemApi -@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) -public final class VirtualCameraMetadata implements Parcelable { - - /** @hide */ - public VirtualCameraMetadata(@NonNull Parcel in) {} - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) {} - - @NonNull - public static final Creator<VirtualCameraMetadata> CREATOR = - new Creator<>() { - @Override - @NonNull - public VirtualCameraMetadata createFromParcel(Parcel in) { - return new VirtualCameraMetadata(in); - } - - @Override - @NonNull - public VirtualCameraMetadata[] newArray(int size) { - return new VirtualCameraMetadata[size]; - } - }; -} diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java index e198821e860e..1272f1683230 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java +++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java @@ -24,6 +24,8 @@ import android.graphics.ImageFormat; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * The configuration of a single virtual camera stream. * @@ -98,6 +100,19 @@ public final class VirtualCameraStreamConfig implements Parcelable { return mHeight; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + VirtualCameraStreamConfig that = (VirtualCameraStreamConfig) o; + return mWidth == that.mWidth && mHeight == that.mHeight && mFormat == that.mFormat; + } + + @Override + public int hashCode() { + return Objects.hash(mWidth, mHeight, mFormat); + } + /** Returns the {@link ImageFormat} of this stream. */ @ImageFormat.Format public int getFormat() { diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 72c436ee6d41..e4af2da57f40 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4940,6 +4940,13 @@ public final class Telephony { */ public static final String COLUMN_IS_NTN = "is_ntn"; + /** + * TelephonyProvider column name to indicate the service capability bitmasks. + * + * @hide + */ + public static final String COLUMN_SERVICE_CAPABILITIES = "service_capabilities"; + /** All columns in {@link SimInfo} table. */ private static final List<String> ALL_COLUMNS = List.of( COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID, @@ -5011,7 +5018,8 @@ public final class Telephony { COLUMN_USER_HANDLE, COLUMN_SATELLITE_ENABLED, COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER, - COLUMN_IS_NTN + COLUMN_IS_NTN, + COLUMN_SERVICE_CAPABILITIES ); /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index cbafd1cfc0cd..ec994590e339 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -32,6 +32,7 @@ import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_H import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE; import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; +import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; import static android.view.flags.Flags.viewVelocityApi; import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR; @@ -2309,6 +2310,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET; private static boolean sToolkitSetFrameRateReadOnlyFlagValue; + private static boolean sToolkitMetricsForFrameRateDecisionFlagValue; static { EMPTY_STATE_SET = StateSet.get(0); @@ -2393,6 +2395,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, | StateSet.VIEW_STATE_PRESSED); sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly(); + sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision(); } /** @@ -33056,7 +33059,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } private float getSizePercentage() { - if (mResources == null || getVisibility() != VISIBLE) { + float alpha = mTransformationInfo != null ? mTransformationInfo.mAlpha : 1; + int visibility = mViewFlags & VISIBILITY_MASK; + + if (mResources == null || alpha == 0 || visibility != VISIBLE) { return 0; } @@ -33084,22 +33090,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ViewRootImpl viewRootImpl = getViewRootImpl(); float sizePercentage = getSizePercentage(); int frameRateCateogry = calculateFrameRateCategory(sizePercentage); - if (sToolkitSetFrameRateReadOnlyFlagValue && viewRootImpl != null - && sizePercentage > 0) { - if (mPreferredFrameRate < 0) { - if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) { - frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE; - } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) { - frameRateCateogry = FRAME_RATE_CATEGORY_LOW; - } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) { - frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL; - } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) { - frameRateCateogry = FRAME_RATE_CATEGORY_HIGH; + if (viewRootImpl != null && sizePercentage > 0) { + if (sToolkitSetFrameRateReadOnlyFlagValue) { + if (mPreferredFrameRate < 0) { + if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) { + frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE; + } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) { + frameRateCateogry = FRAME_RATE_CATEGORY_LOW; + } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) { + frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL; + } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) { + frameRateCateogry = FRAME_RATE_CATEGORY_HIGH; + } + } else { + viewRootImpl.votePreferredFrameRate(mPreferredFrameRate); } - } else { - viewRootImpl.votePreferredFrameRate(mPreferredFrameRate); + viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry); + } + if (sToolkitMetricsForFrameRateDecisionFlagValue) { + viewRootImpl.recordViewPercentage(sizePercentage); } - viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 9f6395e1aab4..487b15c03292 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -252,7 +252,6 @@ import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.function.Predicate; - /** * The top of a view hierarchy, implementing the needed protocol between View * and the WindowManager. This is for the most part an internal implementation @@ -828,6 +827,8 @@ public final class ViewRootImpl implements ViewParent, private boolean mInsetsAnimationRunning; private long mPreviousFrameDrawnTime = -1; + // The largest view size percentage to the display size. Used on trace to collect metric. + private float mLargestChildPercentage = 0.0f; /** * The resolved pointer icon type requested by this window. @@ -1067,6 +1068,7 @@ public final class ViewRootImpl implements ViewParent, private String mTag = TAG; private String mFpsTraceName; + private String mLargestViewTraceName; private static boolean sToolkitSetFrameRateReadOnlyFlagValue; private static boolean sToolkitMetricsForFrameRateDecisionFlagValue; @@ -1318,6 +1320,7 @@ public final class ViewRootImpl implements ViewParent, attrs = mWindowAttributes; setTag(); mFpsTraceName = "FPS of " + getTitle(); + mLargestViewTraceName = "Largest view percentage(per hundred) of " + getTitle(); if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0 @@ -4739,6 +4742,10 @@ public final class ViewRootImpl implements ViewParent, long fps = NANOS_PER_SEC / timeDiff; Trace.setCounter(mFpsTraceName, fps); mPreviousFrameDrawnTime = expectedDrawnTime; + + long percentage = (long) (mLargestChildPercentage * 100); + Trace.setCounter(mLargestViewTraceName, percentage); + mLargestChildPercentage = 0.0f; } private void reportDrawFinished(@Nullable Transaction t, int seqId) { @@ -5059,6 +5066,7 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_FPS) { trackFPS(); } + if (sToolkitMetricsForFrameRateDecisionFlagValue) { collectFrameRateDecisionMetrics(); } @@ -12148,7 +12156,8 @@ public final class ViewRootImpl implements ViewParent, || motionEventAction == MotionEvent.ACTION_UP; boolean undesiredType = windowType == TYPE_INPUT_METHOD; // use toolkitSetFrameRate flag to gate the change - return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue; + return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue + && getFrameRateBoostOnTouchEnabled(); } /** @@ -12223,6 +12232,15 @@ public final class ViewRootImpl implements ViewParent, return mIsFrameRateBoosting; } + /** + * Get the value of mFrameRateBoostOnTouchEnabled + * Can be used to checked if touch boost is enabled. The default value is true. + */ + @VisibleForTesting + public boolean getFrameRateBoostOnTouchEnabled() { + return mWindowAttributes.getFrameRateBoostOnTouchEnabled(); + } + private void boostFrameRate(int boostTimeOut) { mIsFrameRateBoosting = true; setPreferredFrameRateCategory(mPreferredFrameRateCategory); @@ -12252,4 +12270,10 @@ public final class ViewRootImpl implements ViewParent, void setBackKeyCallbackForWindowlessWindow(@NonNull Predicate<KeyEvent> callback) { mWindowlessBackKeyCallback = callback; } + + void recordViewPercentage(float percentage) { + if (!Trace.isEnabled()) return; + // Record the largest view of percentage to the display size. + mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage); + } } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 87537fbc9961..7bae7ec6559c 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -334,6 +334,9 @@ public abstract class Window { private boolean mOverlayWithDecorCaptionEnabled = true; private boolean mCloseOnSwipeEnabled = false; + private static boolean sToolkitSetFrameRateReadOnlyFlagValue = + android.view.flags.Flags.toolkitSetFrameRateReadOnly(); + // The current window attributes. @UnsupportedAppUsage private final WindowManager.LayoutParams mWindowAttributes = @@ -1373,6 +1376,39 @@ public abstract class Window { } /** + * Sets whether the frame rate touch boost is enabled for this Window. + * When enabled, the frame rate will be boosted when a user touches the Window. + * + * @param enabled whether the frame rate touch boost is enabled. + * @see #getFrameRateBoostOnTouchEnabled() + * @see WindowManager.LayoutParams#setFrameRateBoostOnTouchEnabled(boolean) + */ + @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public void setFrameRateBoostOnTouchEnabled(boolean enabled) { + if (sToolkitSetFrameRateReadOnlyFlagValue) { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.setFrameRateBoostOnTouchEnabled(enabled); + dispatchWindowAttributesChanged(attrs); + } + } + + /** + * Get whether frame rate touch boost is enabled + * {@link #setFrameRateBoostOnTouchEnabled(boolean)} + * + * @return whether the frame rate touch boost is enabled. + * @see #setFrameRateBoostOnTouchEnabled(boolean) + * @see WindowManager.LayoutParams#getFrameRateBoostOnTouchEnabled() + */ + @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public boolean getFrameRateBoostOnTouchEnabled() { + if (sToolkitSetFrameRateReadOnlyFlagValue) { + return getAttributes().getFrameRateBoostOnTouchEnabled(); + } + return true; + } + + /** * 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 07a347ace313..f76822f14189 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -4332,6 +4332,13 @@ public interface WindowManager extends ViewManager { private float mDesiredHdrHeadroom = 0; /** + * For variable refresh rate project. + */ + private boolean mFrameRateBoostOnTouch = true; + private static boolean sToolkitSetFrameRateReadOnlyFlagValue = + android.view.flags.Flags.toolkitSetFrameRateReadOnly(); + + /** * Carries the requests about {@link WindowInsetsController.Appearance} and * {@link WindowInsetsController.Behavior} to the system windows which can produce insets. * @@ -4766,6 +4773,32 @@ public interface WindowManager extends ViewManager { } /** + * Set the value whether we should enable Touch Boost + * + * @param enabled Whether we should enable Touch Boost + */ + @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public void setFrameRateBoostOnTouchEnabled(boolean enabled) { + if (sToolkitSetFrameRateReadOnlyFlagValue) { + mFrameRateBoostOnTouch = enabled; + } + } + + /** + * Get the value whether we should enable touch boost as set + * by {@link #setFrameRateBoostOnTouchEnabled(boolean)} + * + * @return A boolean value to indicate whether we should enable touch boost + */ + @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public boolean getFrameRateBoostOnTouchEnabled() { + if (sToolkitSetFrameRateReadOnlyFlagValue) { + return mFrameRateBoostOnTouch; + } + return true; + } + + /** * <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 @@ -4916,6 +4949,9 @@ public interface WindowManager extends ViewManager { out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */); out.writeInt(mDisplayFlags); out.writeFloat(mDesiredHdrHeadroom); + if (sToolkitSetFrameRateReadOnlyFlagValue) { + out.writeBoolean(mFrameRateBoostOnTouch); + } } public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR @@ -4988,6 +5024,9 @@ public interface WindowManager extends ViewManager { paramsForRotation = in.createTypedArray(LayoutParams.CREATOR); mDisplayFlags = in.readInt(); mDesiredHdrHeadroom = in.readFloat(); + if (sToolkitSetFrameRateReadOnlyFlagValue) { + mFrameRateBoostOnTouch = in.readBoolean(); + } } @SuppressWarnings({"PointlessBitwiseExpression"}) @@ -5324,6 +5363,12 @@ public interface WindowManager extends ViewManager { changes |= LAYOUT_CHANGED; } + if (sToolkitSetFrameRateReadOnlyFlagValue + && mFrameRateBoostOnTouch != o.mFrameRateBoostOnTouch) { + mFrameRateBoostOnTouch = o.mFrameRateBoostOnTouch; + changes |= LAYOUT_CHANGED; + } + return changes; } @@ -5546,6 +5591,11 @@ public interface WindowManager extends ViewManager { sb.append(prefix).append(" forciblyShownTypes=").append( WindowInsets.Type.toString(forciblyShownTypes)); } + if (sToolkitSetFrameRateReadOnlyFlagValue && mFrameRateBoostOnTouch) { + sb.append(System.lineSeparator()); + sb.append(prefix).append(" frameRateBoostOnTouch="); + sb.append(mFrameRateBoostOnTouch); + } if (paramsForRotation != null && paramsForRotation.length != 0) { sb.append(System.lineSeparator()); sb.append(prefix).append(" paramsForRotation:"); diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 5bfa3d759a66..7c9340e72f72 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -165,6 +165,9 @@ public final class TransitionInfo implements Parcelable { public static final int FLAGS_IS_NON_APP_WINDOW = FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD | FLAG_IS_SYSTEM_WINDOW; + /** The change will not participate in the animation. */ + public static final int FLAGS_IS_OCCLUDED_NO_ANIMATION = FLAG_IS_OCCLUDED | FLAG_NO_ANIMATION; + /** @hide */ @IntDef(prefix = { "FLAG_" }, value = { FLAG_NONE, diff --git a/core/java/com/android/internal/pm/parsing/IPackageCacher.java b/core/java/com/android/internal/pm/parsing/IPackageCacher.java new file mode 100644 index 000000000000..3e01730ebab6 --- /dev/null +++ b/core/java/com/android/internal/pm/parsing/IPackageCacher.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.internal.pm.parsing; + +import com.android.internal.pm.parsing.pkg.ParsedPackage; + +import java.io.File; + +/** @hide */ +public interface IPackageCacher { + + /** + * Returns the cached parse result for {@code packageFile} for parse flags {@code flags}, + * or {@code null} if no cached result exists. + */ + ParsedPackage getCachedResult(File packageFile, int flags); + + /** + * Caches the parse result for {@code packageFile} with flags {@code flags}. + */ + void cacheResult(File packageFile, int flags, ParsedPackage parsed); +} diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/core/java/com/android/internal/pm/parsing/PackageParser2.java index b6a08a5a546f..e41329340000 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java +++ b/core/java/com/android/internal/pm/parsing/PackageParser2.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package com.android.server.pm.parsing; +package com.android.internal.pm.parsing; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; -import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseInput; @@ -28,26 +27,20 @@ import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.content.res.TypedArray; import android.os.Build; -import android.os.ServiceManager; import android.os.SystemClock; import android.permission.PermissionManager; import android.util.DisplayMetrics; import android.util.Slog; -import com.android.internal.compat.IPlatformCompat; import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.pm.pkg.parsing.ParsingUtils; import com.android.internal.util.ArrayUtils; -import com.android.server.SystemConfig; -import com.android.server.pm.PackageManagerException; -import com.android.server.pm.PackageManagerService; import java.io.File; import java.util.List; -import java.util.Set; /** * The v2 of package parsing for use when parsing is initiated in the server and must @@ -59,50 +52,6 @@ import java.util.Set; */ public class PackageParser2 implements AutoCloseable { - /** - * For parsing inside the system server but outside of {@link PackageManagerService}. - * Generally used for parsing information in an APK that hasn't been installed yet. - * - * This must be called inside the system process as it relies on {@link ServiceManager}. - */ - @NonNull - public static PackageParser2 forParsingFileWithDefaults() { - IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); - return new PackageParser2(null /* separateProcesses */, null /* displayMetrics */, - null /* cacheDir */, new Callback() { - @Override - public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) { - try { - return platformCompat.isChangeEnabled(changeId, appInfo); - } catch (Exception e) { - // This shouldn't happen, but assume enforcement if it does - Slog.wtf(TAG, "IPlatformCompat query failed", e); - return true; - } - } - - @Override - public boolean hasFeature(String feature) { - // Assume the device doesn't support anything. This will affect permission parsing - // and will force <uses-permission/> declarations to include all requiredNotFeature - // permissions and exclude all requiredFeature permissions. This mirrors the old - // behavior. - return false; - } - - @Override - public Set<String> getHiddenApiWhitelistedApps() { - return SystemConfig.getInstance().getHiddenApiWhitelistedApps(); - } - - @Override - public Set<String> getInstallConstraintsAllowlist() { - return SystemConfig.getInstance().getInstallConstraintsAllowlist(); - } - }); - } - private static final String TAG = ParsingUtils.TAG; private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE; @@ -118,12 +67,12 @@ public class PackageParser2 implements AutoCloseable { private final ThreadLocal<ParseTypeImpl> mSharedResult; @Nullable - protected PackageCacher mCacher; + protected IPackageCacher mCacher; - private final ParsingPackageUtils parsingUtils; + private final ParsingPackageUtils mParsingUtils; public PackageParser2(String[] separateProcesses, DisplayMetrics displayMetrics, - @Nullable File cacheDir, @NonNull Callback callback) { + @Nullable IPackageCacher cacher, @NonNull Callback callback) { if (displayMetrics == null) { displayMetrics = new DisplayMetrics(); displayMetrics.setToDefaults(); @@ -134,9 +83,9 @@ public class PackageParser2 implements AutoCloseable { List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager .getSplitPermissions(); - mCacher = cacheDir == null ? null : new PackageCacher(cacheDir); + mCacher = cacher; - parsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions, + mParsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions, callback); ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> { @@ -155,7 +104,7 @@ public class PackageParser2 implements AutoCloseable { */ @AnyThread public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches) - throws PackageManagerException { + throws PackageParserException { var files = packageFile.listFiles(); // Apk directory is directly nested under the current directory if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) { @@ -171,9 +120,9 @@ public class PackageParser2 implements AutoCloseable { long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0; ParseInput input = mSharedResult.get().reset(); - ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags); + ParseResult<ParsingPackage> result = mParsingUtils.parsePackage(input, packageFile, flags); if (result.isError()) { - throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(), + throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(), result.getException()); } @@ -201,12 +150,12 @@ public class PackageParser2 implements AutoCloseable { */ @AnyThread public ParsedPackage parsePackageFromPackageLite(PackageLite packageLite, int flags) - throws PackageManagerException { + throws PackageParserException { ParseInput input = mSharedResult.get().reset(); - ParseResult<ParsingPackage> result = parsingUtils.parsePackageFromPackageLite(input, + ParseResult<ParsingPackage> result = mParsingUtils.parsePackageFromPackageLite(input, packageLite, flags); if (result.isError()) { - throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(), + throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(), result.getException()); } return result.getResult().hideAsParsed(); @@ -226,7 +175,7 @@ public class PackageParser2 implements AutoCloseable { mSharedAppInfo.remove(); } - public static abstract class Callback implements ParsingPackageUtils.Callback { + public abstract static class Callback implements ParsingPackageUtils.Callback { @Override public final ParsingPackage startParsingPackage(@NonNull String packageName, diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java index 2b4123af3885..73d7fe952af3 100644 --- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java +++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java @@ -16,6 +16,9 @@ package android.companion; +import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_PING; +import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING; + import android.content.Context; import android.os.SystemClock; import android.test.InstrumentationTestCase; @@ -36,16 +39,22 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +/** + * Tests that CDM can intake incoming messages in the system data transport and output results. + * + * Build/Install/Run: atest CompanionTests:SystemDataTransportTest + */ public class SystemDataTransportTest extends InstrumentationTestCase { private static final String TAG = "SystemDataTransportTest"; private static final int MESSAGE_INVALID = 0xF00DCAFE; - + private static final int MESSAGE_ONEWAY_INVALID = 0x43434343; // ++++ + private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!! private static final int MESSAGE_REQUEST_INVALID = 0x63636363; // ???? - private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN - private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!! private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI @@ -122,8 +131,6 @@ public class SystemDataTransportTest extends InstrumentationTestCase { new Random().nextBytes(blob); final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, blob); - final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, blob); - assertTransportBehavior(input, expected); } public void testMultiplePingPing() { @@ -176,6 +183,43 @@ public class SystemDataTransportTest extends InstrumentationTestCase { testPingHandRolled(); } + public void testInvalidOnewayMessages() throws InterruptedException { + // Add a callback + final CountDownLatch received = new CountDownLatch(1); + mCdm.addOnMessageReceivedListener(Runnable::run, MESSAGE_ONEWAY_INVALID, + (id, data) -> received.countDown()); + + final byte[] input = generatePacket(MESSAGE_ONEWAY_INVALID, /* sequence */ 1); + final ByteArrayInputStream in = new ByteArrayInputStream(input); + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + mCdm.attachSystemDataTransport(mAssociationId, in, out); + + // Assert that a one-way message was ignored (does not trigger a callback) + assertFalse(received.await(5, TimeUnit.SECONDS)); + + // There should not be a response to one-way messages + assertEquals(0, out.toByteArray().length); + } + + + public void testOnewayMessages() throws InterruptedException { + // Add a callback + final CountDownLatch received = new CountDownLatch(1); + mCdm.addOnMessageReceivedListener(Runnable::run, MESSAGE_ONEWAY_PING, + (id, data) -> received.countDown()); + + final byte[] input = generatePacket(MESSAGE_ONEWAY_PING, /* sequence */ 1); + final ByteArrayInputStream in = new ByteArrayInputStream(input); + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + mCdm.attachSystemDataTransport(mAssociationId, in, out); + + // Assert that a one-way message was received + assertTrue(received.await(1, TimeUnit.SECONDS)); + + // There should not be a response to one-way messages + assertEquals(0, out.toByteArray().length); + } + public static byte[] concat(byte[]... blobs) { int length = 0; for (byte[] blob : blobs) { diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 1577d9c1c1ab..5b0502da1bdf 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -1244,29 +1244,33 @@ public class NotificationTest { } @Test - public void testBigPictureStyle_setExtras_pictureIconNull_noPictureIconKey() { + public void testBigPictureStyle_setExtras_pictureIconNull_pictureIconKeyNull() { Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture((Bitmap) null); Bundle extras = new Bundle(); bpStyle.addExtras(extras); - assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse(); + assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isTrue(); + final Parcelable pictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON); + assertThat(pictureIcon).isNull(); } @Test - public void testBigPictureStyle_setExtras_pictureIconNull_noPictureKey() { + public void testBigPictureStyle_setExtras_pictureIconNull_pictureKeyNull() { Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture((Bitmap) null); Bundle extras = new Bundle(); bpStyle.addExtras(extras); - assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse(); + assertThat(extras.containsKey(EXTRA_PICTURE)).isTrue(); + final Parcelable picture = extras.getParcelable(EXTRA_PICTURE); + assertThat(picture).isNull(); } @Test - public void testBigPictureStyle_setExtras_pictureIconTypeBitmap_noPictureIconKey() { + public void testBigPictureStyle_setExtras_pictureIconTypeBitmap_pictureIconKeyNull() { Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888); Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture(bitmap); @@ -1274,11 +1278,13 @@ public class NotificationTest { Bundle extras = new Bundle(); bpStyle.addExtras(extras); - assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse(); + assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isTrue(); + final Parcelable pictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON); + assertThat(pictureIcon).isNull(); } @Test - public void testBigPictureStyle_setExtras_pictureIconTypeIcon_noPictureKey() { + public void testBigPictureStyle_setExtras_pictureIconTypeIcon_pictureKeyNull() { Icon icon = Icon.createWithResource(mContext, R.drawable.btn_plus); Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture(icon); @@ -1286,7 +1292,9 @@ public class NotificationTest { Bundle extras = new Bundle(); bpStyle.addExtras(extras); - assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse(); + assertThat(extras.containsKey(EXTRA_PICTURE)).isTrue(); + final Parcelable picture = extras.getParcelable(EXTRA_PICTURE); + assertThat(picture).isNull(); } @Test diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index b30a0c8fbbd3..cf3eb12498ca 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -659,8 +659,6 @@ public class ViewRootImplTest { ViewRootImpl viewRootImpl = view.getViewRootImpl(); sInstrumentation.runOnMainSync(() -> { view.invalidate(); - assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(), - FRAME_RATE_CATEGORY_NORMAL); viewRootImpl.notifyInsetsAnimationRunningStateChanged(true); view.invalidate(); }); @@ -672,6 +670,37 @@ public class ViewRootImplTest { }); } + + /** + * Test FrameRateBoostOnTouchEnabled API + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public void votePreferredFrameRate_frameRateBoostOnTouch() { + View view = new View(sContext); + attachViewToWindow(view); + sInstrumentation.waitForIdleSync(); + + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + final WindowManager.LayoutParams attrs = viewRootImpl.mWindowAttributes; + assertEquals(attrs.getFrameRateBoostOnTouchEnabled(), true); + assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(), + attrs.getFrameRateBoostOnTouchEnabled()); + + sInstrumentation.runOnMainSync(() -> { + attrs.setFrameRateBoostOnTouchEnabled(false); + viewRootImpl.setLayoutParams(attrs, false); + }); + sInstrumentation.waitForIdleSync(); + + sInstrumentation.runOnMainSync(() -> { + final WindowManager.LayoutParams newAttrs = viewRootImpl.mWindowAttributes; + assertEquals(newAttrs.getFrameRateBoostOnTouchEnabled(), false); + assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(), + newAttrs.getFrameRateBoostOnTouchEnabled()); + }); + } + @Test public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() { mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 69a6e6d998a4..c6f920f55c07 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -518,6 +518,8 @@ applications that come with the platform <permission name="android.permission.ACCESS_BROADCAST_RADIO"/> <!-- Permission required for CTS test - CtsAmbientContextServiceTestCases --> <permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/> + <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases --> + <permission name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"/> <!-- Permission required for CTS test - CtsTelephonyProviderTestCases --> <permission name="android.permission.WRITE_APN_SETTINGS"/> <!-- Permission required for GTS test - GtsStatsdHostTestCases --> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java deleted file mode 100644 index ff49cdcab349..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java +++ /dev/null @@ -1,119 +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 androidx.window.extensions.embedding; - -import static java.util.Objects.requireNonNull; - -import android.graphics.Rect; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -/** - * The parameter to create an overlay container that retrieved from - * {@link android.app.ActivityOptions} bundle. - */ -class OverlayCreateParams { - - // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack. - @VisibleForTesting - static final String KEY_OVERLAY_CREATE_PARAMS = - "androidx.window.extensions.OverlayCreateParams"; - - @VisibleForTesting - static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID = - "androidx.window.extensions.OverlayCreateParams.taskId"; - - @VisibleForTesting - static final String KEY_OVERLAY_CREATE_PARAMS_TAG = - "androidx.window.extensions.OverlayCreateParams.tag"; - - @VisibleForTesting - static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS = - "androidx.window.extensions.OverlayCreateParams.bounds"; - - private final int mTaskId; - - @NonNull - private final String mTag; - - @NonNull - private final Rect mBounds; - - OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) { - mTaskId = taskId; - mTag = requireNonNull(tag); - mBounds = requireNonNull(bounds); - } - - int getTaskId() { - return mTaskId; - } - - @NonNull - String getTag() { - return mTag; - } - - @NonNull - Rect getBounds() { - return mBounds; - } - - @Override - public int hashCode() { - int result = mTaskId; - result = 31 * result + mTag.hashCode(); - result = 31 * result + mBounds.hashCode(); - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (!(obj instanceof OverlayCreateParams thatParams)) return false; - return mTaskId == thatParams.mTaskId - && mTag.equals(thatParams.mTag) - && mBounds.equals(thatParams.mBounds); - } - - @Override - public String toString() { - return OverlayCreateParams.class.getSimpleName() + ": {" - + "taskId=" + mTaskId - + ", tag=" + mTag - + ", bounds=" + mBounds - + "}"; - } - - /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */ - @Nullable - static OverlayCreateParams fromBundle(@NonNull Bundle bundle) { - final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS); - if (paramsBundle == null) { - return null; - } - final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID); - final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG)); - final Rect bounds = requireNonNull(paramsBundle.getParcelable( - KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class)); - - return new OverlayCreateParams(taskId, tag, bounds); - } -} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 4973a4d85af7..15ee4e1d4adf 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -34,6 +34,7 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHA import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; +import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG; import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule; @@ -136,6 +137,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private Function<SplitAttributesCalculatorParams, SplitAttributes> mSplitAttributesCalculator; /** + * A calculator function to compute {@link ActivityStack} attributes in a task, which is called + * when there's {@link #onTaskFragmentParentInfoChanged} or folding state changed. + */ + @GuardedBy("mLock") + @Nullable + private Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes> + mActivityStackAttributesCalculator; + + /** * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info * below it. * When the app is host of multiple Tasks, there can be multiple splits controlled by the same @@ -319,6 +329,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } + @Override + public void setActivityStackAttributesCalculator( + @NonNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes> + calculator) { + synchronized (mLock) { + mActivityStackAttributesCalculator = calculator; + } + } + + @Override + public void clearActivityStackAttributesCalculator() { + synchronized (mLock) { + mActivityStackAttributesCalculator = null; + } + } + @GuardedBy("mLock") @Nullable Function<SplitAttributesCalculatorParams, SplitAttributes> getSplitAttributesCalculator() { @@ -1412,7 +1438,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @Nullable Activity launchingActivity) { return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity, - null /* overlayTag */); + null /* overlayTag */, null /* launchOptions */); } /** @@ -1426,7 +1452,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen TaskFragmentContainer createEmptyContainer( @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @NonNull Rect bounds, @Nullable Activity launchingActivity, - @Nullable String overlayTag) { + @Nullable String overlayTag, @Nullable Bundle launchOptions) { // We need an activity in the organizer process in the same Task to use as the owner // activity, as well as to get the Task window info. final Activity activityInTask; @@ -1443,7 +1469,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */, - intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag); + intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag, + launchOptions); final IBinder taskFragmentToken = container.getTaskFragmentToken(); // Note that taskContainer will not exist before calling #newContainer if the container // is the first embedded TF in the task. @@ -1570,14 +1597,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, @NonNull Activity activityInTask, int taskId) { return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */, - activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */); + activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */, + null /* launchOptions */); } @GuardedBy("mLock") TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) { return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, - activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */); + activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */, + null /* launchOptions */); } @GuardedBy("mLock") @@ -1585,7 +1614,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull Activity activityInTask, int taskId, @NonNull TaskFragmentContainer pairedPrimaryContainer) { return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, - activityInTask, taskId, pairedPrimaryContainer, null /* tag */); + activityInTask, taskId, pairedPrimaryContainer, null /* tag */, + null /* launchOptions */); } /** @@ -1602,11 +1632,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * @param overlayTag The tag for the new created overlay container. It must be * needed if {@code isOverlay} is {@code true}. Otherwise, * it should be {@code null}. + * @param launchOptions The launch options bundle to create a container. Must be + * specified for overlay container. */ @GuardedBy("mLock") TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId, - @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) { + @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag, + @Nullable Bundle launchOptions) { if (activityInTask == null) { throw new IllegalArgumentException("activityInTask must not be null,"); } @@ -1615,7 +1648,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } final TaskContainer taskContainer = mTaskContainers.get(taskId); final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity, - pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag); + pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag, + launchOptions); return container; } @@ -2345,28 +2379,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") @Nullable TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded( - @NonNull WindowContainerTransaction wct, - @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId, + @NonNull WindowContainerTransaction wct, @NonNull Bundle options, @NonNull Intent intent, @NonNull Activity launchActivity) { - final int taskId = overlayCreateParams.getTaskId(); - if (taskId != launchTaskId) { - // The task ID doesn't match the launch activity's. Cannot determine the host task - // to launch the overlay. - throw new IllegalArgumentException("The task ID of " - + "OverlayCreateParams#launchingActivity must match the task ID of " - + "the activity to #startActivity with the activity options that takes " - + "OverlayCreateParams."); - } final List<TaskFragmentContainer> overlayContainers = getAllOverlayTaskFragmentContainers(); - final String overlayTag = overlayCreateParams.getTag(); + final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG)); // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions // specified by Intent, expand the overlay container to fill the parent task instead. - final Rect bounds = overlayCreateParams.getBounds(); - final Size minDimensions = getMinDimensions(intent); - final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds, - minDimensions); + final ActivityStackAttributesCalculatorParams params = + new ActivityStackAttributesCalculatorParams(mPresenter.toParentContainerInfo( + mPresenter.getTaskProperties(launchActivity)), overlayTag, options); + // Fallback to expand the bounds if there's no activityStackAttributes calculator. + final Rect relativeBounds = mActivityStackAttributesCalculator != null + ? new Rect(mActivityStackAttributesCalculator.apply(params).getRelativeBounds()) + : new Rect(); + final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(relativeBounds, + getMinDimensions(intent)); + // Expand the bounds if the requested bounds are smaller than minimum dimensions. + if (shouldExpandContainer) { + relativeBounds.setEmpty(); + } + final int taskId = getTaskId(launchActivity); if (!overlayContainers.isEmpty()) { for (final TaskFragmentContainer overlayContainer : overlayContainers) { if (!overlayTag.equals(overlayContainer.getOverlayTag()) @@ -2390,7 +2424,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties() .getTaskMetrics().getBounds(); final IBinder overlayToken = overlayContainer.getTaskFragmentToken(); - final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds); + final Rect sanitizedBounds = sanitizeBounds(relativeBounds, intent, taskBounds); mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds); mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken, !sanitizedBounds.isEmpty()); @@ -2402,8 +2436,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } } - return createEmptyContainer(wct, intent, taskId, - (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag); + // Launch the overlay container to the task with taskId. + return createEmptyContainer(wct, intent, taskId, relativeBounds, launchActivity, overlayTag, + options); } private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter { @@ -2568,12 +2603,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer launchedInTaskFragment; if (launchingActivity != null) { final int taskId = getTaskId(launchingActivity); - final OverlayCreateParams overlayCreateParams = - OverlayCreateParams.fromBundle(options); + final String overlayTag = options.getString(KEY_OVERLAY_TAG); if (Flags.activityEmbeddingOverlayPresentationFlag() - && overlayCreateParams != null) { + && overlayTag != null) { launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct, - overlayCreateParams, taskId, intent, launchingActivity); + options, intent, launchingActivity); } else { launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent, launchingActivity); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index b5c32bbe78fa..acfd8e4314bf 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -1084,4 +1084,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) { return getTaskProperties(activity).getTaskMetrics(); } + + @NonNull + ParentContainerInfo toParentContainerInfo(@NonNull TaskProperties taskProperties) { + final Configuration configuration = taskProperties.getConfiguration(); + final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent + .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(), + configuration.windowConfiguration); + return new ParentContainerInfo(taskProperties.getTaskMetrics(), configuration, + windowLayoutInfo); + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index ed8c4f31306b..da87339b01a7 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -24,6 +24,7 @@ import android.app.WindowConfiguration.WindowingMode; import android.content.Intent; import android.graphics.Rect; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.util.Size; import android.window.TaskFragmentAnimationParams; @@ -105,6 +106,13 @@ class TaskFragmentContainer { @Nullable private final String mOverlayTag; + /** + * The launch options that was used to create this container. Must not be {@code null} for + * {@link #isOverlay()} container. + */ + @Nullable + private final Bundle mLaunchOptions; + /** Indicates whether the container was cleaned up after the last activity was removed. */ private boolean mIsFinished; @@ -165,7 +173,7 @@ class TaskFragmentContainer { /** * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController, - * TaskFragmentContainer, String) + * TaskFragmentContainer, String, Bundle) */ TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @@ -173,7 +181,8 @@ class TaskFragmentContainer { @NonNull SplitController controller, @Nullable TaskFragmentContainer pairedPrimaryContainer) { this(pendingAppearedActivity, pendingAppearedIntent, taskContainer, - controller, pairedPrimaryContainer, null /* overlayTag */); + controller, pairedPrimaryContainer, null /* overlayTag */, + null /* launchOptions */); } /** @@ -181,11 +190,14 @@ class TaskFragmentContainer { * container transaction. * @param pairedPrimaryContainer when it is set, the new container will be add right above it * @param overlayTag Sets to indicate this taskFragment is an overlay container + * @param launchOptions The launch options to create this container. Must not be + * {@code null} for an overlay container */ TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, @NonNull SplitController controller, - @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) { + @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag, + @Nullable Bundle launchOptions) { if ((pendingAppearedActivity == null && pendingAppearedIntent == null) || (pendingAppearedActivity != null && pendingAppearedIntent != null)) { throw new IllegalArgumentException( @@ -195,6 +207,10 @@ class TaskFragmentContainer { mToken = new Binder("TaskFragmentContainer"); mTaskContainer = taskContainer; mOverlayTag = overlayTag; + if (overlayTag != null) { + Objects.requireNonNull(launchOptions); + } + mLaunchOptions = launchOptions; if (pairedPrimaryContainer != null) { // The TaskFragment will be positioned right above the paired container. @@ -903,8 +919,8 @@ class TaskFragmentContainer { } /** - * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this - * taskFragment container is not an overlay container. + * Returns the tag specified in launch options. {@code null} if this taskFragment container is + * not an overlay container. */ @Nullable String getOverlayTag() { diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 4c2433fab2f8..678bdef3df92 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -18,14 +18,11 @@ package androidx.window.extensions.embedding; import static android.view.Display.DEFAULT_DISPLAY; +import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder; -import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS; -import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS; -import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG; -import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -45,6 +42,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import android.app.Activity; +import android.app.ActivityOptions; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -98,9 +96,6 @@ public class OverlayPresentationTest { @Rule public final SetFlagsRule mSetFlagRule = new SetFlagsRule(); - private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS = - new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200)); - private SplitController.ActivityStartMonitor mMonitor; private Intent mIntent; @@ -165,37 +160,15 @@ public class OverlayPresentationTest { } @Test - public void testOverlayCreateParamsFromBundle() { - assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull(); - - assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle())) - .isEqualTo(TEST_OVERLAY_CREATE_PARAMS); - } - - @Test public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() { mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG); - mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle()); + final Bundle optionsBundle = ActivityOptions.makeBasic().toBundle(); + optionsBundle.putString(KEY_OVERLAY_TAG, "test"); + mMonitor.onStartActivity(mActivity, mIntent, optionsBundle); verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(), - anyInt(), any(), any()); - } - - @NonNull - private static Bundle createOverlayCreateParamsTestBundle() { - final Bundle bundle = new Bundle(); - - final Bundle paramsBundle = new Bundle(); - paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID, - TEST_OVERLAY_CREATE_PARAMS.getTaskId()); - paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag()); - paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS, - TEST_OVERLAY_CREATE_PARAMS.getBounds()); - - bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle); - - return bundle; + any(), any()); } @Test @@ -221,19 +194,11 @@ public class OverlayPresentationTest { } @Test - public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() { - assertThrows("The method must return null due to task mismatch between" - + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class, - () -> createOrUpdateOverlayTaskFragmentIfNeeded( - TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1)); - } - - @Test public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() { createExistingOverlayContainers(); - final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( - new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID); + final TaskFragmentContainer overlayContainer = + createOrUpdateOverlayTaskFragmentIfNeeded("test3"); assertWithMessage("overlayContainer1 must be dismissed since the new overlay container" + " is launched to the same task") @@ -245,9 +210,9 @@ public class OverlayPresentationTest { public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() { createExistingOverlayContainers(); - final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( - new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)), - TASK_ID + 2); + doReturn(TASK_ID + 2).when(mActivity).getTaskId(); + final TaskFragmentContainer overlayContainer = + createOrUpdateOverlayTaskFragmentIfNeeded("test1"); assertWithMessage("overlayContainer1 must be dismissed since the new overlay container" + " is launched with the same tag as an existing overlay container in a different " @@ -261,9 +226,10 @@ public class OverlayPresentationTest { createExistingOverlayContainers(); final Rect bounds = new Rect(0, 0, 100, 100); + mSplitController.setActivityStackAttributesCalculator(params -> + new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build()); final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( - new OverlayCreateParams(TASK_ID, "test1", bounds), - TASK_ID); + "test1"); assertWithMessage("overlayContainer1 must be updated since the new overlay container" + " is launched with the same tag and task") @@ -279,9 +245,8 @@ public class OverlayPresentationTest { public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() { createExistingOverlayContainers(); - final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( - new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)), - TASK_ID); + final TaskFragmentContainer overlayContainer = + createOrUpdateOverlayTaskFragmentIfNeeded("test2"); // OverlayContainer1 is dismissed since new container is launched in the same task with // different tag. OverlayContainer2 is dismissed since new container is launched with the @@ -304,8 +269,11 @@ public class OverlayPresentationTest { mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(), MinimumDimensionActivity.class)); - final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( - TEST_OVERLAY_CREATE_PARAMS, TASK_ID); + final Rect bounds = new Rect(0, 0, 100, 100); + mSplitController.setActivityStackAttributesCalculator(params -> + new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build()); + final TaskFragmentContainer overlayContainer = + createOrUpdateOverlayTaskFragmentIfNeeded("test"); final IBinder overlayToken = overlayContainer.getTaskFragmentToken(); assertThat(mSplitController.getAllOverlayTaskFragmentContainers()) @@ -316,7 +284,7 @@ public class OverlayPresentationTest { // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case. clearInvocations(mSplitPresenter); - createOrUpdateOverlayTaskFragmentIfNeeded(TEST_OVERLAY_CREATE_PARAMS, TASK_ID); + createOrUpdateOverlayTaskFragmentIfNeeded("test"); verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect()); verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken, @@ -329,11 +297,11 @@ public class OverlayPresentationTest { public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() { final Rect bounds = new Rect(TASK_BOUNDS); bounds.offset(10, 10); - final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID, - "test", bounds); + mSplitController.setActivityStackAttributesCalculator(params -> + new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build()); - final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( - paramsOutsideTaskBounds, TASK_ID); + final TaskFragmentContainer overlayContainer = + createOrUpdateOverlayTaskFragmentIfNeeded("test"); final IBinder overlayToken = overlayContainer.getTaskFragmentToken(); assertThat(mSplitController.getAllOverlayTaskFragmentContainers()) @@ -344,7 +312,7 @@ public class OverlayPresentationTest { // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case. clearInvocations(mSplitPresenter); - createOrUpdateOverlayTaskFragmentIfNeeded(paramsOutsideTaskBounds, TASK_ID); + createOrUpdateOverlayTaskFragmentIfNeeded("test"); verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect()); verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken, @@ -355,15 +323,17 @@ public class OverlayPresentationTest { @Test public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() { - final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( - TEST_OVERLAY_CREATE_PARAMS, TASK_ID); + final Rect bounds = new Rect(0, 0, 100, 100); + mSplitController.setActivityStackAttributesCalculator(params -> + new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build()); + final TaskFragmentContainer overlayContainer = + createOrUpdateOverlayTaskFragmentIfNeeded("test"); assertThat(mSplitController.getAllOverlayTaskFragmentContainers()) .containsExactly(overlayContainer); assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID); - assertThat(overlayContainer - .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue(); - assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag()); + assertThat(overlayContainer.areLastRequestedBoundsEqual(bounds)).isTrue(); + assertThat(overlayContainer.getOverlayTag()).isEqualTo("test"); } @Test @@ -416,12 +386,14 @@ public class OverlayPresentationTest { @Test public void testGetTopNonFinishingActivityWithOverlay() { - createTestOverlayContainer(TASK_ID, "test1"); + TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test1"); + final Activity activity = createMockActivity(); final TaskFragmentContainer container = createMockTaskFragmentContainer(activity); final TaskContainer task = container.getTaskContainer(); - assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */)).isEqualTo(mActivity); + assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */)) + .isEqualTo(overlayContainer.getTopNonFinishingActivity()); assertThat(task.getTopNonFinishingActivity(false /* includeOverlay */)).isEqualTo(activity); } @@ -458,10 +430,11 @@ public class OverlayPresentationTest { * #createOrUpdateOverlayTaskFragmentIfNeeded} */ @Nullable - private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded( - @NonNull OverlayCreateParams params, int taskId) { - return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params, - taskId, mIntent, mActivity); + private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(@NonNull String tag) { + final Bundle launchOptions = new Bundle(); + launchOptions.putString(KEY_OVERLAY_TAG, tag); + return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, + launchOptions, mIntent, mActivity); } /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */ @@ -475,10 +448,11 @@ public class OverlayPresentationTest { @NonNull private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) { + Activity activity = createMockActivity(); TaskFragmentContainer overlayContainer = mSplitController.newContainer( - null /* pendingAppearedActivity */, mIntent, mActivity, taskId, - null /* pairedPrimaryContainer */, tag); - setupTaskFragmentInfo(overlayContainer, mActivity); + null /* pendingAppearedActivity */, mIntent, activity, taskId, + null /* pairedPrimaryContainer */, tag, Bundle.EMPTY); + setupTaskFragmentInfo(overlayContainer, activity); return overlayContainer; } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 8c274a26177d..bab4e9195880 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -590,7 +590,7 @@ public class SplitControllerTest { assertFalse(result); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(), - anyString()); + anyString(), any()); } @Test @@ -753,7 +753,7 @@ public class SplitControllerTest { assertTrue(result); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(), - anyString()); + anyString(), any()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); } @@ -796,7 +796,7 @@ public class SplitControllerTest { assertTrue(result); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(), - anyString()); + anyString(), any()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index d8c691b01b61..a49823648d01 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -70,9 +70,11 @@ import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -124,6 +126,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private final Context mContext; private final ContentResolver mContentResolver; private final ShellController mShellController; + private final ShellCommandHandler mShellCommandHandler; private final ShellExecutor mShellExecutor; private final Handler mBgHandler; @@ -180,7 +183,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull @ShellBackgroundThread Handler backgroundHandler, Context context, @NonNull BackAnimationBackground backAnimationBackground, - ShellBackAnimationRegistry shellBackAnimationRegistry) { + ShellBackAnimationRegistry shellBackAnimationRegistry, + ShellCommandHandler shellCommandHandler) { this( shellInit, shellController, @@ -190,7 +194,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont context, context.getContentResolver(), backAnimationBackground, - shellBackAnimationRegistry); + shellBackAnimationRegistry, + shellCommandHandler); } @VisibleForTesting @@ -203,7 +208,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont Context context, ContentResolver contentResolver, @NonNull BackAnimationBackground backAnimationBackground, - ShellBackAnimationRegistry shellBackAnimationRegistry) { + ShellBackAnimationRegistry shellBackAnimationRegistry, + ShellCommandHandler shellCommandHandler) { mShellController = shellController; mShellExecutor = shellExecutor; mActivityTaskManager = activityTaskManager; @@ -219,6 +225,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont .build(); mShellBackAnimationRegistry = shellBackAnimationRegistry; mLatencyTracker = LatencyTracker.getInstance(mContext); + mShellCommandHandler = shellCommandHandler; } private void onInit() { @@ -227,6 +234,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont createAdapter(); mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION, this::createExternalInterface, this); + mShellCommandHandler.addDumpCallback(this::dump, this); } private void setupAnimationDeveloperSettingsObserver( @@ -968,4 +976,20 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont }; mBackAnimationAdapter = new BackAnimationAdapter(runner); } + + /** + * Description of current BackAnimationController state. + */ + private void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "BackAnimationController state:"); + pw.println(prefix + " mEnableAnimations=" + mEnableAnimations.get()); + pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted); + pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress); + pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent); + pw.println(prefix + " mCurrentTracker state:"); + mCurrentTracker.dump(pw, prefix + " "); + pw.println(prefix + " mQueuedTracker state:"); + mQueuedTracker.dump(pw, prefix + " "); + } + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java index 765428dabd9c..30d5edb59c85 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java @@ -211,6 +211,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation { private void finishAnimation() { if (mEnteringTarget != null) { + mTransaction.setCornerRadius(mEnteringTarget.leash, 0); mEnteringTarget.leash.release(); mEnteringTarget = null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java index ace792244778..6213f628dfd3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java @@ -24,6 +24,8 @@ import android.view.RemoteAnimationTarget; import android.window.BackEvent; import android.window.BackMotionEvent; +import java.io.PrintWriter; + /** * Helper class to record the touch location for gesture and generate back events. */ @@ -221,6 +223,12 @@ class TouchTracker { mNonLinearFactor = nonLinearFactor; } + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "TouchTracker state:"); + pw.println(prefix + " mState=" + mState); + pw.println(prefix + " mTriggerBack=" + mTriggerBack); + } + enum TouchTrackerState { INITIAL, ACTIVE, FINISHED } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index b7f749e8a8b6..470a82511481 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1291,7 +1291,7 @@ public class BubbleStackView extends FrameLayout // We only show user education for conversation bubbles right now return false; } - final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION); + final boolean seen = getPrefBoolean(ManageEducationView.PREF_MANAGED_EDUCATION); final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext)) && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null; if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { @@ -1342,7 +1342,7 @@ public class BubbleStackView extends FrameLayout // We only show user education for conversation bubbles right now return false; } - final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION); + final boolean seen = getPrefBoolean(StackEducationView.PREF_STACK_EDUCATION); final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext); if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { Log.d(TAG, "Show stack edu: " + shouldShow); @@ -2323,7 +2323,8 @@ public class BubbleStackView extends FrameLayout updateOverflowVisibility(); updatePointerPosition(false /* forIme */); mExpandedAnimationController.expandFromStack(() -> { - if (mIsExpanded && mExpandedBubble.getExpandedView() != null) { + if (mIsExpanded && mExpandedBubble != null + && mExpandedBubble.getExpandedView() != null) { maybeShowManageEdu(); } updateOverflowDotVisibility(true /* expanding */); @@ -2384,7 +2385,7 @@ public class BubbleStackView extends FrameLayout } mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); - if (mExpandedBubble.getExpandedView() != null) { + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { mExpandedBubble.getExpandedView().setContentAlpha(0f); mExpandedBubble.getExpandedView().setBackgroundAlpha(0f); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt index 61e17c8ec459..da71b1c741bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt @@ -33,15 +33,16 @@ import com.android.wm.shell.animation.Interpolators * User education view to highlight the manage button that allows a user to configure the settings * for the bubble. Shown only the first time a user expands a bubble. */ -class ManageEducationView(context: Context, positioner: BubblePositioner) : LinearLayout(context) { - - private val TAG = - if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView" - else BubbleDebugConfig.TAG_BUBBLES - - private val ANIMATE_DURATION: Long = 200 +class ManageEducationView( + context: Context, + private val positioner: BubblePositioner +) : LinearLayout(context) { + + companion object { + const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding" + private const val ANIMATE_DURATION: Long = 200 + } - private val positioner: BubblePositioner = positioner private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) } private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) } private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) } @@ -128,7 +129,7 @@ class ManageEducationView(context: Context, positioner: BubblePositioner) : Line .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .alpha(1f) } - setShouldShow(false) + updateManageEducationSeen() } /** @@ -218,13 +219,11 @@ class ManageEducationView(context: Context, positioner: BubblePositioner) : Line } } - private fun setShouldShow(shouldShow: Boolean) { + private fun updateManageEducationSeen() { context .getSharedPreferences(context.packageName, Context.MODE_PRIVATE) .edit() - .putBoolean(PREF_MANAGED_EDUCATION, !shouldShow) + .putBoolean(PREF_MANAGED_EDUCATION, true) .apply() } } - -const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt index 2cabb65abe7a..95f101722e89 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt @@ -34,19 +34,15 @@ import com.android.wm.shell.animation.Interpolators */ class StackEducationView( context: Context, - positioner: BubblePositioner, - controller: BubbleController + private val positioner: BubblePositioner, + private val controller: BubbleController ) : LinearLayout(context) { - private val TAG = - if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView" - else BubbleDebugConfig.TAG_BUBBLES - - private val ANIMATE_DURATION: Long = 200 - private val ANIMATE_DURATION_SHORT: Long = 40 - - private val positioner: BubblePositioner = positioner - private val controller: BubbleController = controller + companion object { + const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding" + private const val ANIMATE_DURATION: Long = 200 + private const val ANIMATE_DURATION_SHORT: Long = 40 + } private val view by lazy { requireViewById<View>(R.id.stack_education_layout) } private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) } @@ -175,7 +171,7 @@ class StackEducationView( .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .alpha(1f) } - setShouldShow(false) + updateStackEducationSeen() return true } @@ -196,13 +192,11 @@ class StackEducationView( .withEndAction { visibility = GONE } } - private fun setShouldShow(shouldShow: Boolean) { + private fun updateStackEducationSeen() { context .getSharedPreferences(context.packageName, Context.MODE_PRIVATE) .edit() - .putBoolean(PREF_STACK_EDUCATION, !shouldShow) + .putBoolean(PREF_STACK_EDUCATION, true) .apply() } } - -const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java index 5b0239f6d659..02af2d06a1dd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java @@ -563,7 +563,7 @@ public class ExpandedAnimationController ? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR : p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR; animationForChild(child) - .translationX(fromX, p.y) + .translationX(fromX, p.x) .start(); } else { float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR; @@ -634,4 +634,9 @@ public class ExpandedAnimationController .start(); } } + + /** Returns true if we're in the middle of a collapse or expand animation. */ + boolean isAnimating() { + return mAnimatingCollapse || mAnimatingExpand; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index f794fef48f27..893a87fe4885 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -48,12 +48,14 @@ public class BubbleBarAnimationHelper { private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f; private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150; private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 100; + private static final int EXPANDED_VIEW_ANIMATE_POSITION_DURATION = 300; + private static final int EXPANDED_VIEW_DISMISS_DURATION = 250; + private static final int EXPANDED_VIEW_DRAG_ANIMATION_DURATION = 150; /** * Additional scale applied to expanded view when it is positioned inside a magnetic target. */ - private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.75f; - private static final int EXPANDED_VIEW_ANIMATE_POSITION_DURATION = 300; - private static final int EXPANDED_VIEW_DISMISS_DURATION = 250; + private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.6f; + private static final float EXPANDED_VIEW_DRAG_SCALE = 0.5f; /** Spring config for the expanded view scale-in animation. */ private final PhysicsAnimator.SpringConfig mScaleInSpringConfig = @@ -72,6 +74,7 @@ public class BubbleBarAnimationHelper { private final Context mContext; private final BubbleBarLayerView mLayerView; private final BubblePositioner mPositioner; + private final int[] mTmpLocation = new int[2]; private BubbleViewProvider mExpandedBubble; private boolean mIsExpanded = false; @@ -220,6 +223,25 @@ public class BubbleBarAnimationHelper { } /** + * Animate the expanded bubble when it is being dragged + */ + public void animateStartDrag() { + final BubbleBarExpandedView bbev = getExpandedView(); + if (bbev == null) { + Log.w(TAG, "Trying to animate start drag without a bubble"); + return; + } + bbev.setPivotX(bbev.getWidth() / 2f); + bbev.setPivotY(0f); + bbev.animate() + .scaleX(EXPANDED_VIEW_DRAG_SCALE) + .scaleY(EXPANDED_VIEW_DRAG_SCALE) + .setInterpolator(Interpolators.EMPHASIZED) + .setDuration(EXPANDED_VIEW_DRAG_ANIMATION_DURATION) + .start(); + } + + /** * Animates dismissal of currently expanded bubble * * @param endRunnable a runnable to run at the end of the animation @@ -261,7 +283,10 @@ public class BubbleBarAnimationHelper { .setDuration(EXPANDED_VIEW_ANIMATE_POSITION_DURATION) .setInterpolator(Interpolators.EMPHASIZED_DECELERATE) .withStartAction(() -> bbev.setAnimating(true)) - .withEndAction(() -> bbev.setAnimating(false)) + .withEndAction(() -> { + bbev.setAnimating(false); + bbev.resetPivot(); + }) .start(); } @@ -277,25 +302,52 @@ public class BubbleBarAnimationHelper { Log.w(TAG, "Trying to snap the expanded view to target without a bubble"); return; } - Point expandedViewCenter = getViewCenterOnScreen(bbev); - - // Calculate the difference between the target's center coordinates and the object's. - // Animating the object's x/y properties by these values will center the object on top - // of the magnetic target. - float xDiff = target.getCenterOnScreen().x - expandedViewCenter.x; - float yDiff = target.getCenterOnScreen().y - expandedViewCenter.y; // Calculate scale of expanded view so it fits inside the magnetic target float bbevMaxSide = Math.max(bbev.getWidth(), bbev.getHeight()); - float targetMaxSide = Math.max(target.getTargetView().getWidth(), - target.getTargetView().getHeight()); - float scale = (targetMaxSide * EXPANDED_VIEW_IN_TARGET_SCALE) / bbevMaxSide; + View targetView = target.getTargetView(); + float targetMaxSide = Math.max(targetView.getWidth(), targetView.getHeight()); + // Reduce target size to have some padding between the target and expanded view + targetMaxSide *= EXPANDED_VIEW_IN_TARGET_SCALE; + float scaleInTarget = targetMaxSide / bbevMaxSide; + + // Scale around the top center of the expanded view. Same as when dragging. + bbev.setPivotX(bbev.getWidth() / 2f); + bbev.setPivotY(0); + + // When the view animates into the target, it is scaled down with the pivot at center top. + // Find the point on the view that would be the center of the view at its final scale. + // Once we know that, we can calculate x and y distance from the center of the target view + // and use that for the translation animation to ensure that the view at final scale is + // placed at the center of the target. + + // Set mTmpLocation to the current location of the view on the screen, taking into account + // any scale applied. + bbev.getLocationOnScreen(mTmpLocation); + // Since pivotX is at the center of the x-axis, even at final scale, center of the view on + // x-axis will be the same as the center of the view at current size. + // Get scaled width of the view and adjust mTmpLocation so that point on x-axis is at the + // center of the view at its current size. + float currentWidth = bbev.getWidth() * bbev.getScaleX(); + mTmpLocation[0] += currentWidth / 2; + // Since pivotY is at the top of the view, at final scale, top coordinate of the view + // remains the same. + // Get height of the view at final scale and adjust mTmpLocation so that point on y-axis is + // moved down by half of the height at final scale. + float targetHeight = bbev.getHeight() * scaleInTarget; + mTmpLocation[1] += targetHeight / 2; + // mTmpLocation is now set to the point on the view that will be the center of the view once + // scale is applied. + + // Calculate the difference between the target's center coordinates and mTmpLocation + float xDiff = target.getCenterOnScreen().x - mTmpLocation[0]; + float yDiff = target.getCenterOnScreen().y - mTmpLocation[1]; bbev.animate() .translationX(bbev.getTranslationX() + xDiff) .translationY(bbev.getTranslationY() + yDiff) - .scaleX(scale) - .scaleY(scale) + .scaleX(scaleInTarget) + .scaleY(scaleInTarget) .setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION) .setInterpolator(Interpolators.EMPHASIZED) .withStartAction(() -> bbev.setAnimating(true)) @@ -319,8 +371,8 @@ public class BubbleBarAnimationHelper { } expandedView .animate() - .scaleX(1f) - .scaleY(1f) + .scaleX(EXPANDED_VIEW_DRAG_SCALE) + .scaleY(EXPANDED_VIEW_DRAG_SCALE) .setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION) .setInterpolator(Interpolators.EMPHASIZED) .withStartAction(() -> expandedView.setAnimating(true)) @@ -385,12 +437,4 @@ public class BubbleBarAnimationHelper { final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded); return new Size(width, height); } - - private Point getViewCenterOnScreen(View view) { - Point center = new Point(); - int[] onScreenLocation = view.getLocationOnScreen(); - center.x = (int) (onScreenLocation[0] + (view.getWidth() / 2f)); - center.y = (int) (onScreenLocation[1] + (view.getHeight() / 2f)); - return center; - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt index d21545079cc2..5e634a23955a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt @@ -74,6 +74,9 @@ class BubbleBarExpandedViewDragController( } private inner class HandleDragListener : RelativeTouchListener() { + + private var isMoving = false + override fun onDown(v: View, ev: MotionEvent): Boolean { // While animating, don't allow new touch events return !expandedView.isAnimating @@ -87,6 +90,10 @@ class BubbleBarExpandedViewDragController( dx: Float, dy: Float ) { + if (!isMoving) { + isMoving = true + animationHelper.animateStartDrag() + } expandedView.translationX = expandedViewInitialTranslationX + dx expandedView.translationY = expandedViewInitialTranslationY + dy dismissView.show() @@ -114,6 +121,7 @@ class BubbleBarExpandedViewDragController( animationHelper.animateToRestPosition() dismissView.hide() } + isMoving = false } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 3c6bc1754c5c..fc97c7988a0f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -363,7 +363,8 @@ public abstract class WMShellBaseModule { @ShellMainThread ShellExecutor shellExecutor, @ShellBackgroundThread Handler backgroundHandler, BackAnimationBackground backAnimationBackground, - Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry) { + Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry, + ShellCommandHandler shellCommandHandler) { if (BackAnimationController.IS_ENABLED) { return shellBackAnimationRegistry.map( (animations) -> @@ -374,7 +375,8 @@ public abstract class WMShellBaseModule { backgroundHandler, context, backAnimationBackground, - animations)); + animations, + shellCommandHandler)); } return Optional.empty(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index af69b5272ad5..b0d8b47b170a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -757,6 +757,10 @@ public class Transitions implements RemoteCallable<Transitions>, } if (!change.hasFlags(FLAG_IS_OCCLUDED)) { allOccluded = false; + } else if (change.hasAllFlags(TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION)) { + // Remove the change because it should be invisible in the animation. + info.getChanges().remove(i); + continue; } // The change has already animated by back gesture, don't need to play transition // animation on it. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 771876f7ce5d..9ded6ea1d187 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -63,6 +63,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.util.test.FakeSettingsProvider; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; +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.sysui.ShellSharedConstants; @@ -110,6 +111,8 @@ public class BackAnimationControllerTest extends ShellTestCase { @Mock private InputManager mInputManager; + @Mock + private ShellCommandHandler mShellCommandHandler; private BackAnimationController mController; private TestableContentResolver mContentResolver; @@ -145,7 +148,8 @@ public class BackAnimationControllerTest extends ShellTestCase { mContext, mContentResolver, mAnimationBackground, - mShellBackAnimationRegistry); + mShellBackAnimationRegistry, + mShellCommandHandler); mShellInit.init(); mShellExecutor.flushAll(); } @@ -298,7 +302,8 @@ public class BackAnimationControllerTest extends ShellTestCase { mContext, mContentResolver, mAnimationBackground, - mShellBackAnimationRegistry); + mShellBackAnimationRegistry, + mShellCommandHandler); shellInit.init(); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java index c1ff260836b8..60f1d271c3af 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java @@ -16,52 +16,51 @@ package com.android.wm.shell.bubbles.animation; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.annotation.SuppressLint; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PointF; import android.graphics.Rect; -import android.testing.AndroidTestingRunner; import android.view.View; import android.view.WindowManager; import android.widget.FrameLayout; import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleStackView; +import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase { - private int mDisplayWidth = 500; - private int mDisplayHeight = 1000; - - private Runnable mOnBubbleAnimatedOutAction = mock(Runnable.class); + private final Semaphore mBubbleRemovedSemaphore = new Semaphore(0); + private final Runnable mOnBubbleAnimatedOutAction = mBubbleRemovedSemaphore::release; ExpandedAnimationController mExpandedController; private int mStackOffset; private PointF mExpansionPoint; private BubblePositioner mPositioner; - private BubbleStackView.StackViewState mStackViewState = new BubbleStackView.StackViewState(); + private final BubbleStackView.StackViewState mStackViewState = + new BubbleStackView.StackViewState(); - @SuppressLint("VisibleForTests") @Before public void setUp() throws Exception { super.setUp(); @@ -70,15 +69,13 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC getContext().getSystemService(WindowManager.class)); mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT, Insets.of(0, 0, 0, 0), - new Rect(0, 0, mDisplayWidth, mDisplayHeight)); + new Rect(0, 0, 500, 1000)); BubbleStackView stackView = mock(BubbleStackView.class); - when(stackView.getState()).thenReturn(getStackViewState()); mExpandedController = new ExpandedAnimationController(mPositioner, mOnBubbleAnimatedOutAction, stackView); - spyOn(mExpandedController); addOneMoreThanBubbleLimitBubbles(); mLayout.setActiveController(mExpandedController); @@ -86,9 +83,18 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC Resources res = mLayout.getResources(); mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); mExpansionPoint = new PointF(100, 100); + + getStackViewState(); + when(stackView.getState()).thenAnswer(i -> getStackViewState()); + waitForMainThread(); } - public BubbleStackView.StackViewState getStackViewState() { + @After + public void tearDown() { + waitForMainThread(); + } + + private BubbleStackView.StackViewState getStackViewState() { mStackViewState.numberOfBubbles = mLayout.getChildCount(); mStackViewState.selectedIndex = 0; mStackViewState.onLeft = mPositioner.isStackOnLeft(mExpansionPoint); @@ -96,68 +102,71 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC } @Test - @Ignore - public void testExpansionAndCollapse() throws InterruptedException { - Runnable afterExpand = mock(Runnable.class); - mExpandedController.expandFromStack(afterExpand); - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); - + public void testExpansionAndCollapse() throws Exception { + expand(); testBubblesInCorrectExpandedPositions(); - verify(afterExpand).run(); + waitForMainThread(); - Runnable afterCollapse = mock(Runnable.class); + final Semaphore semaphore = new Semaphore(0); + Runnable afterCollapse = semaphore::release; mExpandedController.collapseBackToStack(mExpansionPoint, false, afterCollapse); - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); - - testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1); - verify(afterExpand).run(); + assertThat(semaphore.tryAcquire(1, 2, TimeUnit.SECONDS)).isTrue(); + waitForAnimation(); + testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y); } @Test - @Ignore - public void testOnChildAdded() throws InterruptedException { + public void testOnChildAdded() throws Exception { expand(); + waitForMainThread(); // Add another new view and wait for its animation. final View newView = new FrameLayout(getContext()); mLayout.addView(newView, 0); - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + waitForAnimation(); testBubblesInCorrectExpandedPositions(); } @Test - @Ignore - public void testOnChildRemoved() throws InterruptedException { + public void testOnChildRemoved() throws Exception { expand(); + waitForMainThread(); - // Remove some views and see if the remaining child views still pass the expansion test. + // Remove some views and verify the remaining child views still pass the expansion test. mLayout.removeView(mViews.get(0)); mLayout.removeView(mViews.get(3)); - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Removing a view will invoke onBubbleAnimatedOutAction. Block until it gets called twice. + assertThat(mBubbleRemovedSemaphore.tryAcquire(2, 2, TimeUnit.SECONDS)).isTrue(); + + waitForAnimation(); testBubblesInCorrectExpandedPositions(); } @Test - public void testDragBubbleOutDoesntNPE() throws InterruptedException { + public void testDragBubbleOutDoesntNPE() { mExpandedController.onGestureFinished(); mExpandedController.dragBubbleOut(mViews.get(0), 1, 1); } /** Expand the stack and wait for animations to finish. */ private void expand() throws InterruptedException { - mExpandedController.expandFromStack(mock(Runnable.class)); - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + final Semaphore semaphore = new Semaphore(0); + Runnable afterExpand = semaphore::release; + + mExpandedController.expandFromStack(afterExpand); + assertThat(semaphore.tryAcquire(1, TimeUnit.SECONDS)).isTrue(); } /** Check that children are in the correct positions for being stacked. */ - private void testStackedAtPosition(float x, float y, int offsetMultiplier) { + private void testStackedAtPosition(float x, float y) { // Make sure the rest of the stack moved again, including the first bubble not moving, and // is stacked to the right now that we're on the right side of the screen. for (int i = 0; i < mLayout.getChildCount(); i++) { - assertEquals(x + i * offsetMultiplier * mStackOffset, - mLayout.getChildAt(i).getTranslationX(), 2f); - assertEquals(y, mLayout.getChildAt(i).getTranslationY(), 2f); + assertEquals(x, mLayout.getChildAt(i).getTranslationX(), 2f); + assertEquals(y + Math.min(i, 1) * mStackOffset, mLayout.getChildAt(i).getTranslationY(), + 2f); assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f); } } @@ -175,4 +184,22 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC mLayout.getChildAt(i).getTranslationY(), 2f); } } + + private void waitForAnimation() throws Exception { + final Semaphore semaphore = new Semaphore(0); + boolean[] animating = new boolean[]{ true }; + for (int i = 0; i < 4; i++) { + if (animating[0]) { + mMainThreadHandler.post(() -> { + if (!mExpandedController.isAnimating()) { + animating[0] = false; + semaphore.release(); + } + }); + Thread.sleep(500); + } + } + assertThat(semaphore.tryAcquire(1, 2, TimeUnit.SECONDS)).isTrue(); + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java index 48ae2961b4be..2ed5addd900c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java @@ -164,11 +164,17 @@ public class PhysicsAnimationLayoutTestCase extends ShellTestCase { @Override public void cancelAllAnimations() { + if (mLayout.getChildCount() == 0) { + return; + } mMainThreadHandler.post(super::cancelAllAnimations); } @Override public void cancelAnimationsOnView(View view) { + if (mLayout.getChildCount() == 0) { + return; + } mMainThreadHandler.post(() -> super.cancelAnimationsOnView(view)); } @@ -221,6 +227,9 @@ public class PhysicsAnimationLayoutTestCase extends ShellTestCase { @Override protected void startPathAnimation() { + if (mLayout.getChildCount() == 0) { + return; + } mMainThreadHandler.post(super::startPathAnimation); } } @@ -322,4 +331,9 @@ public class PhysicsAnimationLayoutTestCase extends ShellTestCase { e.printStackTrace(); } } + + /** Waits for the main thread to finish processing all pending runnables. */ + public void waitForMainThread() { + runOnMainThreadAndBlock(() -> {}); + } } diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig index b6055e818f8c..f4b1056019cb 100644 --- a/location/java/android/location/flags/gnss.aconfig +++ b/location/java/android/location/flags/gnss.aconfig @@ -20,3 +20,10 @@ flag { description: "Flag for GnssMeasurementRequest WorkSource API" bug: "295235160" } + +flag { + name: "release_supl_connection_on_timeout" + namespace: "location" + description: "Flag for releasing SUPL connection on timeout" + bug: "315024652" +} diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index 5a72b0be3405..1a3d7b7d5868 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -23,6 +23,8 @@ import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.util.SparseIntArray; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; @@ -402,7 +404,9 @@ public final class AudioDeviceInfo { private final AudioDevicePort mPort; - AudioDeviceInfo(AudioDevicePort port) { + /** @hide */ + @VisibleForTesting + public AudioDeviceInfo(AudioDevicePort port) { mPort = port; } diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java index 73bc6f96bf8b..2de8eefc4e78 100644 --- a/media/java/android/media/AudioDevicePort.java +++ b/media/java/android/media/AudioDevicePort.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; +import com.android.aconfig.annotations.VisibleForTesting; + import java.util.Arrays; import java.util.List; @@ -38,6 +40,26 @@ import java.util.List; public class AudioDevicePort extends AudioPort { + /** @hide */ + // TODO: b/316864909 - Remove this method once there's a way to fake audio device ports further + // down the stack. + @VisibleForTesting + public static AudioDevicePort createForTesting( + int type, @NonNull String name, @NonNull String address) { + return new AudioDevicePort( + new AudioHandle(/* id= */ 0), + name, + /* samplingRates= */ null, + /* channelMasks= */ null, + /* channelIndexMasks= */ null, + /* formats= */ null, + /* gains= */ null, + type, + address, + /* encapsulationModes= */ null, + /* encapsulationMetadataTypes= */ null); + } + private final int mType; private final String mAddress; private final int[] mEncapsulationModes; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 3dfd5726455d..a5a69f987113 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -22,6 +22,7 @@ import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.media.audio.Flags.automaticBtDeviceType; import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API; +import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION; import android.Manifest; import android.annotation.CallbackExecutor; @@ -5120,6 +5121,71 @@ public class AudioManager { } /** + * Notifies an application with a focus listener of gain or loss of audio focus + * + * <p>This is similar to {@link #dispatchAudioFocusChange(AudioFocusInfo, int, AudioPolicy)} but + * with additional functionality of fade. The players of the application with audio focus + * change, provided they meet the active {@link FadeManagerConfiguration} requirements, are + * faded before dispatching the callback to the application. For example, players of the + * application losing audio focus will be faded out, whereas players of the application gaining + * audio focus will be faded in, if needed. + * + * <p>The applicability of fade is decided against the supplied active {@link AudioFocusInfo}. + * This list cannot be {@code null}. The list can be empty if no other active + * {@link AudioFocusInfo} available at the time of the dispatch. + * + * <p>The {@link FadeManagerConfiguration} supplied here is prioritized over existing fade + * configurations. If none supplied, either the {@link FadeManagerConfiguration} set through + * {@link AudioPolicy} or the default will be used to determine the fade properties. + * + * <p>This method can only be used by owners of an {@link AudioPolicy} configured with + * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to true. + * + * @param afi the recipient of the focus change, that has previously requested audio focus, and + * that was received by the {@code AudioPolicy} through + * {@link AudioPolicy.AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)} + * @param focusChange one of focus gain types ({@link #AUDIOFOCUS_GAIN}, + * {@link #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or + * {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}) + * or one of the focus loss types ({@link AudioManager#AUDIOFOCUS_LOSS}, + * {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT}, + * or {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}). + * <br>For the focus gain, the change type should be the same as the app requested + * @param ap a valid registered {@link AudioPolicy} configured as a focus policy. + * @param otherActiveAfis active {@link AudioFocusInfo} that are granted audio focus at the time + * of dispatch + * @param transientFadeMgrConfig {@link FadeManagerConfiguration} that will be used for fading + * players resulting from this dispatch. This is a transient configuration that is only + * valid for this focus change and shall be discarded after processing this request. + * @return {@link #AUDIOFOCUS_REQUEST_FAILED} if the focus client didn't have a listener or if + * there was an error sending the request, or {@link #AUDIOFOCUS_REQUEST_GRANTED} if the + * dispatch was successfully sent, or {@link #AUDIOFOCUS_REQUEST_DELAYED} if + * the request was successful but the dispatch of focus change was delayed due to a fade + * operation. + * @throws NullPointerException if the {@link AudioFocusInfo} or {@link AudioPolicy} or list of + * other active {@link AudioFocusInfo} are {@code null}. + * @hide + */ + @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION) + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public int dispatchAudioFocusChangeWithFade(@NonNull AudioFocusInfo afi, int focusChange, + @NonNull AudioPolicy ap, @NonNull List<AudioFocusInfo> otherActiveAfis, + @Nullable FadeManagerConfiguration transientFadeMgrConfig) { + Objects.requireNonNull(afi, "AudioFocusInfo cannot be null"); + Objects.requireNonNull(ap, "AudioPolicy cannot be null"); + Objects.requireNonNull(otherActiveAfis, "Other active AudioFocusInfo list cannot be null"); + + IAudioService service = getService(); + try { + return service.dispatchFocusChangeWithFade(afi, focusChange, ap.cb(), otherActiveAfis, + transientFadeMgrConfig); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @hide * Used internally by telephony package to abandon audio focus, typically after a call or * when ringing ends and the call is rejected or not answered. diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java index 337d4b0a916c..40b0e3e03ef6 100644 --- a/media/java/android/media/FadeManagerConfiguration.java +++ b/media/java/android/media/FadeManagerConfiguration.java @@ -16,12 +16,13 @@ package android.media; -import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION; +import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; @@ -93,11 +94,9 @@ import java.util.Objects; * Helps with recreating a new instance from another to simply change/add on top of the * existing ones</li> * </ul> - * TODO(b/304835727): Convert into system API so that it can be set through AudioPolicy - * * @hide */ - +@SystemApi @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION) public final class FadeManagerConfiguration implements Parcelable { @@ -523,6 +522,7 @@ public final class FadeManagerConfiguration implements Parcelable { * * @param fadeState one of the fade state in {@link FadeStateEnum} * @return human-readable string + * @hide */ @NonNull public static String fadeStateToString(@FadeStateEnum int fadeState) { @@ -712,7 +712,8 @@ public final class FadeManagerConfiguration implements Parcelable { * * <p><b>Notes:</b> * <ul> - * <li>When fade state is set to enabled, the builder expects at least one valid usage to be + * <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT} or + * {@link #FADE_STATE_ENABLED_AUTO}, the builder expects at least one valid usage to be * set/added. Failure to do so will result in an exception during {@link #build()}</li> * <li>Every usage added to the fadeable list should have corresponding volume shaper * configs defined. This can be achieved by setting either the duration or volume shaper @@ -720,8 +721,8 @@ public final class FadeManagerConfiguration implements Parcelable { * {@link #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)}</li> * <li> It is recommended to set volume shaper configurations individually for fade out and * fade in</li> - * <li>For any incomplete volume shaper configs a volume shaper configuration will be - * created using either the default fade durations or the ones provided as part of the + * <li>For any incomplete volume shaper configurations, a volume shaper configuration will + * be created using either the default fade durations or the ones provided as part of the * {@link #Builder(long, long)}</li> * <li>Additional volume shaper configs can also configured for a given usage * with additional attributes like content-type in order to achieve finer fade controls. diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index a52f0b08330d..5c268d4ab652 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -28,6 +28,7 @@ import android.media.AudioPlaybackConfiguration; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.BluetoothProfileConnectionInfo; +import android.media.FadeManagerConfiguration; import android.media.IAudioDeviceVolumeDispatcher; import android.media.IAudioFocusDispatcher; import android.media.IAudioModeDispatcher; @@ -398,6 +399,14 @@ interface IAudioService { int dispatchFocusChange(in AudioFocusInfo afi, in int focusChange, in IAudioPolicyCallback pcb); + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)") + int dispatchFocusChangeWithFade(in AudioFocusInfo afi, + in int focusChange, + in IAudioPolicyCallback pcb, + in List<AudioFocusInfo> otherActiveAfis, + in FadeManagerConfiguration transientFadeMgrConfig); + oneway void playerHasOpPlayAudio(in int piid, in boolean hasOpPlayAudio); @EnforcePermission("BLUETOOTH_STACK") @@ -754,4 +763,16 @@ interface IAudioService { oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo); PersistableBundle getLoudnessParams(int piid, in LoudnessCodecInfo codecInfo); + + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)") + int setFadeManagerConfigurationForFocusLoss(in FadeManagerConfiguration fmcForFocusLoss); + + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)") + int clearFadeManagerConfigurationForFocusLoss(); + + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)") + FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss(); } diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index e16849811b9d..b85decc74ff8 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -16,6 +16,8 @@ package android.media.audiopolicy; +import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION; + import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; @@ -34,6 +36,7 @@ import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.AudioTrack; +import android.media.FadeManagerConfiguration; import android.media.IAudioService; import android.media.MediaRecorder; import android.media.projection.MediaProjection; @@ -49,6 +52,7 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -612,6 +616,110 @@ public class AudioPolicy { return mRegistrationId; } + /** + * Sets a custom {@link FadeManagerConfiguration} to handle fade cycle of players during + * {@link android.media.AudioManager#AUDIOFOCUS_LOSS} + * + * @param fmcForFocusLoss custom {@link FadeManagerConfiguration} + * @return {@link AudioManager#SUCCESS} if the update was successful, + * {@link AudioManager#ERROR} otherwise + * @throws IllegalStateException if the audio policy is not registered + * @hide + */ + @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION) + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @SystemApi + public int setFadeManagerConfigurationForFocusLoss( + @NonNull FadeManagerConfiguration fmcForFocusLoss) { + Objects.requireNonNull(fmcForFocusLoss, + "FadeManagerConfiguration for focus loss cannot be null"); + + IAudioService service = getService(); + synchronized (mLock) { + Preconditions.checkState(isAudioPolicyRegisteredLocked(), + "Cannot set FadeManagerConfiguration with unregistered AudioPolicy"); + + try { + return service.setFadeManagerConfigurationForFocusLoss(fmcForFocusLoss); + } catch (RemoteException e) { + Log.e(TAG, "Received remote exception for setFadeManagerConfigurationForFocusLoss:", + e); + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Clear the current {@link FadeManagerConfiguration} set to handle fade cycles of players + * during {@link android.media.AudioManager#AUDIOFOCUS_LOSS} + * + * <p>In the absence of custom {@link FadeManagerConfiguration}, the default configurations will + * be used to handle fade cycles during audio focus loss. + * + * @return {@link AudioManager#SUCCESS} if the update was successful, + * {@link AudioManager#ERROR} otherwise + * @throws IllegalStateException if the audio policy is not registered + * @see #setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration) + * @hide + */ + @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION) + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @SystemApi + public int clearFadeManagerConfigurationForFocusLoss() { + IAudioService service = getService(); + synchronized (mLock) { + Preconditions.checkState(isAudioPolicyRegisteredLocked(), + "Cannot clear FadeManagerConfiguration from unregistered AudioPolicy"); + + try { + return service.clearFadeManagerConfigurationForFocusLoss(); + } catch (RemoteException e) { + Log.e(TAG, "Received remote exception for " + + "clearFadeManagerConfigurationForFocusLoss:", e); + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Get the current fade manager configuration used for fade operations during + * {@link android.media.AudioManager#AUDIOFOCUS_LOSS} + * + * <p>If no custom {@link FadeManagerConfiguration} is set, the default configuration currently + * active will be returned. + * + * @return the active {@link FadeManagerConfiguration} used during audio focus loss + * @throws IllegalStateException if the audio policy is not registered + * @see #setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration) + * @see #clearFadeManagerConfigurationForFocusLoss() + * @hide + */ + @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION) + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @SystemApi + @NonNull + public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() { + IAudioService service = getService(); + synchronized (mLock) { + Preconditions.checkState(isAudioPolicyRegisteredLocked(), + "Cannot get FadeManagerConfiguration from unregistered AudioPolicy"); + + try { + return service.getFadeManagerConfigurationForFocusLoss(); + } catch (RemoteException e) { + Log.e(TAG, "Received remote exception for getFadeManagerConfigurationForFocusLoss:", + e); + throw e.rethrowFromSystemServer(); + + } + } + } + + @GuardedBy("mLock") + private boolean isAudioPolicyRegisteredLocked() { + return mStatus == POLICY_STATUS_REGISTERED; + } + private boolean policyReadyToUse() { synchronized (mLock) { if (mStatus != POLICY_STATUS_REGISTERED) { diff --git a/media/java/android/media/flags/fade_manager_configuration.aconfig b/media/java/android/media/flags/fade_manager_configuration.aconfig deleted file mode 100644 index 100e2235a7a8..000000000000 --- a/media/java/android/media/flags/fade_manager_configuration.aconfig +++ /dev/null @@ -1,8 +0,0 @@ -package: "com.android.media.flags" - -flag { - namespace: "media_solutions" - name: "enable_fade_manager_configuration" - description: "Enable Fade Manager Configuration support to determine fade properties" - bug: "307354764" -}
\ No newline at end of file diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java index fb6bd489d5d0..f105ae9cc33e 100644 --- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java @@ -16,7 +16,7 @@ package com.android.audiopolicytest; -import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION; +import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION; import static org.junit.Assert.assertThrows; diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt index 66bd6f502274..d5cf1a35b4df 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt @@ -26,6 +26,7 @@ import androidx.compose.material.icons.outlined.Error import androidx.compose.material.icons.outlined.PowerOff import androidx.compose.material.icons.outlined.Shield import androidx.compose.material.icons.outlined.WarningAmber +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -78,24 +79,19 @@ object CardPageProvider : SettingsPageProvider { imageVector = Icons.Outlined.WarningAmber, buttons = listOf( CardButton(text = "Action") {}, - CardButton(text = "Action", isMain = true) {}, - ) + ), + tintColor = MaterialTheme.colorScheme.error, + containerColor = MaterialTheme.colorScheme.errorContainer, ) ) } @Composable private fun SettingsCardWithoutIcon() { - var isVisible by rememberSaveable { mutableStateOf(true) } SettingsCard( CardModel( title = stringResource(R.string.sample_title), text = stringResource(R.string.sample_text), - isVisible = { isVisible }, - onDismiss = { isVisible = false }, - buttons = listOf( - CardButton(text = "Action") {}, - ), ) ) } @@ -104,6 +100,7 @@ object CardPageProvider : SettingsPageProvider { fun SampleSettingsCollapsibleCard() { val context = LocalContext.current var isVisible0 by rememberSaveable { mutableStateOf(true) } + var isVisible1 by rememberSaveable { mutableStateOf(true) } val cards = remember { mutableStateListOf( CardModel( @@ -114,16 +111,17 @@ object CardPageProvider : SettingsPageProvider { onDismiss = { isVisible0 = false }, buttons = listOf( CardButton(text = "Action") {}, - ) + ), ), CardModel( title = context.getString(R.string.sample_title), text = context.getString(R.string.sample_text), imageVector = Icons.Outlined.Shield, + isVisible = { isVisible1 }, + onDismiss = { isVisible1 = false }, buttons = listOf( CardButton(text = "Action") {}, - CardButton(text = "Main action", isMain = true) {}, - ) + ), ) ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index 993cb4ac85e5..c143390f269c 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -37,7 +37,6 @@ object SettingsDimension { val itemPaddingAround = 8.dp val itemDividerHeight = 32.dp - val iconSmall = 16.dp val iconLarge = 48.dp /** The size when app icon is displayed in list. */ diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt index b18a1bc01388..b2a8b87a4495 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt @@ -16,11 +16,11 @@ package com.android.settingslib.spa.widget.card +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector data class CardButton( val text: String, - val isMain: Boolean = false, val onClick: () -> Unit, ) @@ -38,4 +38,10 @@ data class CardModel( val onDismiss: (() -> Unit)? = null, val buttons: List<CardButton> = emptyList(), + + /** If specified, this color will be used to tint the icon and the buttons. */ + val tintColor: Color = Color.Unspecified, + + /** If specified, this color will be used to tint the icon and the buttons. */ + val containerColor: Color = Color.Unspecified, ) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt index 7eec8888f025..c7845fa724d4 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt @@ -23,26 +23,26 @@ import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.WarningAmber -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -72,11 +72,14 @@ fun SettingsCard(content: @Composable ColumnScope.() -> Unit) { } @Composable -fun SettingsCardContent(content: @Composable ColumnScope.() -> Unit) { +fun SettingsCardContent( + containerColor: Color = Color.Unspecified, + content: @Composable ColumnScope.() -> Unit, +) { Card( shape = CornerExtraSmall, colors = CardDefaults.cardColors( - containerColor = SettingsTheme.colorScheme.surface, + containerColor = containerColor.takeOrElse { SettingsTheme.colorScheme.surface }, ), modifier = Modifier .fillMaxWidth() @@ -95,37 +98,43 @@ fun SettingsCard(model: CardModel) { @Composable internal fun SettingsCardImpl(model: CardModel) { AnimatedVisibility(visible = model.isVisible()) { - SettingsCardContent { + SettingsCardContent(containerColor = model.containerColor) { Column( - modifier = Modifier.padding(SettingsDimension.itemPaddingStart), + modifier = Modifier.padding( + horizontal = SettingsDimension.dialogItemPaddingHorizontal, + vertical = SettingsDimension.itemPaddingAround, + ), verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround) ) { - CardHeader(model.imageVector, model.onDismiss) + CardHeader(model.imageVector, model.tintColor, model.onDismiss) SettingsTitle(model.title) SettingsBody(model.text) - Buttons(model.buttons) + Buttons(model.buttons, model.tintColor) } } } } @Composable -fun CardHeader(imageVector: ImageVector?, onDismiss: (() -> Unit)? = null) { +fun CardHeader(imageVector: ImageVector?, iconColor: Color, onDismiss: (() -> Unit)? = null) { + if (imageVector != null || onDismiss != null) { + Spacer(Modifier.height(SettingsDimension.buttonPaddingVertical)) + } Row(Modifier.fillMaxWidth()) { - CardIcon(imageVector) + CardIcon(imageVector, iconColor) Spacer(modifier = Modifier.weight(1f)) DismissButton(onDismiss) } } @Composable -private fun CardIcon(imageVector: ImageVector?) { +private fun CardIcon(imageVector: ImageVector?, color: Color) { if (imageVector != null) { Icon( imageVector = imageVector, contentDescription = null, modifier = Modifier.size(SettingsDimension.itemIconSize), - tint = MaterialTheme.colorScheme.primary, + tint = color.takeOrElse { MaterialTheme.colorScheme.primary }, ) } } @@ -146,52 +155,35 @@ private fun DismissButton(onDismiss: (() -> Unit)?) { contentDescription = stringResource( androidx.compose.material3.R.string.m3c_snackbar_dismiss ), - modifier = Modifier.size(SettingsDimension.iconSmall), + modifier = Modifier.padding(SettingsDimension.paddingSmall), ) } } } @Composable -private fun Buttons(buttons: List<CardButton>) { +private fun Buttons(buttons: List<CardButton>, color: Color) { if (buttons.isNotEmpty()) { Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = SettingsDimension.itemPaddingAround), + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy( space = SettingsDimension.itemPaddingEnd, alignment = Alignment.End, ), ) { for (button in buttons) { - Button(button) + Button(button, color) } } + } else { + Spacer(Modifier.height(SettingsDimension.itemPaddingAround)) } } @Composable -private fun Button(button: CardButton) { - if (button.isMain) { - Button( - onClick = button.onClick, - colors = ButtonDefaults.buttonColors( - containerColor = SettingsTheme.colorScheme.primaryContainer, - ), - ) { - Text( - text = button.text, - color = SettingsTheme.colorScheme.onPrimaryContainer, - ) - } - } else { - OutlinedButton(onClick = button.onClick) { - Text( - text = button.text, - color = MaterialTheme.colorScheme.onSurface, - ) - } +private fun Button(button: CardButton, color: Color) { + TextButton(onClick = button.onClick) { + Text(text = button.text, color = color) } } @@ -206,7 +198,6 @@ private fun SettingsCardPreview() { imageVector = Icons.Outlined.WarningAmber, buttons = listOf( CardButton(text = "Action") {}, - CardButton(text = "Action", isMain = true) {}, ) ) ) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt index 6e36490beac7..c34df653f03c 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt @@ -141,7 +141,6 @@ private fun SettingsCollapsibleCardPreview() { imageVector = Icons.Outlined.Shield, buttons = listOf( CardButton(text = "Action") {}, - CardButton(text = "Main action", isMain = true) {}, ) ) ) diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java index c51a9a07332f..8e5396fef744 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java @@ -23,8 +23,6 @@ import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AF import static com.android.settingslib.Utils.getColorAttrDefaultColor; import android.Manifest; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -52,6 +50,8 @@ import android.util.Log; import android.view.MenuItem; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index 3b8f66577f6e..70ece0fa8076 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -21,7 +21,6 @@ import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLED_B import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; -import android.annotation.NonNull; import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.content.Context; @@ -35,6 +34,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceViewHolder; diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 107d5f8a8ae9..c2be571b444a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -3,7 +3,6 @@ package com.android.settingslib; import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL; import android.annotation.ColorInt; -import android.annotation.Nullable; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; @@ -23,10 +22,10 @@ import android.graphics.ColorFilter; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.drawable.Drawable; -import android.hardware.usb.flags.Flags; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; +import android.hardware.usb.flags.Flags; import android.location.LocationManager; import android.media.AudioManager; import android.net.NetworkCapabilities; @@ -47,6 +46,7 @@ import android.telephony.TelephonyManager; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.graphics.drawable.RoundedBitmapDrawable; import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java index dae48dbac0a4..b0db16fa2e30 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java @@ -17,7 +17,6 @@ package com.android.settingslib.applications; import android.app.AppGlobals; -import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -28,6 +27,8 @@ import android.graphics.drawable.Drawable; import android.os.RemoteException; import android.util.IconDrawableFactory; +import androidx.annotation.Nullable; + import com.android.settingslib.widget.CandidateInfo; /** diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java index fa056e2b77bd..416b36981a4c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -26,9 +26,9 @@ import static android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF; import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON; import android.annotation.IntDef; -import android.annotation.Nullable; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java index 19005755ef58..cf4d6be9a042 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java @@ -21,8 +21,6 @@ import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; import android.annotation.CallbackExecutor; import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothCsipSetCoordinator; @@ -35,6 +33,9 @@ import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.settingslib.R; import java.lang.annotation.Retention; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java index c9512cd01aa3..8eaea0e3561b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java @@ -151,7 +151,9 @@ public class HearingAidAudioRoutingHelper { private boolean removePreferredDeviceForStrategies(List<AudioProductStrategy> strategies) { boolean status = true; for (AudioProductStrategy strategy : strategies) { - status &= mAudioManager.removePreferredDeviceForStrategy(strategy); + if (mAudioManager.getPreferredDeviceForStrategy(strategy) != null) { + status &= mAudioManager.removePreferredDeviceForStrategy(strategy); + } } return status; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java index 51164e836590..57012aabb123 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java @@ -21,7 +21,6 @@ import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_ALL; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; -import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -31,6 +30,7 @@ import android.content.Context; import android.os.Build; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.settingslib.R; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 49ac0f864ed7..de21c541c7e4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -19,7 +19,6 @@ package com.android.settingslib.bluetooth; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; import android.annotation.CallbackExecutor; -import android.annotation.NonNull; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -44,6 +43,7 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java index 34008ac56042..34c60a1b9f34 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java @@ -20,7 +20,6 @@ import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; import android.annotation.CallbackExecutor; -import android.annotation.NonNull; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -33,6 +32,7 @@ import android.content.Context; import android.os.Build; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import com.android.settingslib.R; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java index 3774b88db93d..ab7a3db4b3bb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java @@ -21,7 +21,6 @@ import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; import android.annotation.CallbackExecutor; import android.annotation.IntRange; -import android.annotation.NonNull; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -31,6 +30,7 @@ import android.content.Context; import android.os.Build; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import java.util.ArrayList; diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java index be420ac8bd24..43c6c0dbd1e5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java @@ -16,7 +16,6 @@ package com.android.settingslib.connectivity; -import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -31,6 +30,7 @@ import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; /** diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java index 067afa4ff726..f84715487788 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java @@ -14,7 +14,6 @@ package com.android.settingslib.core.instrumentation; -import android.annotation.Nullable; import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Context; @@ -24,6 +23,7 @@ import android.os.AsyncTask; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.util.Map; diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java index 2bd0b27063ea..e974e7036945 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java @@ -22,13 +22,13 @@ import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; import static androidx.lifecycle.Lifecycle.Event.ON_START; import static androidx.lifecycle.Lifecycle.Event.ON_STOP; -import android.annotation.Nullable; import android.app.Activity; import android.os.Bundle; import android.os.PersistableBundle; import android.view.Menu; import android.view.MenuItem; +import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.LifecycleOwner; diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java index bbb1ec6a5623..0029e20ba626 100644 --- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java +++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java @@ -16,7 +16,6 @@ package com.android.settingslib.datetime; -import android.annotation.Nullable; import android.content.Context; import android.content.res.XmlResourceParser; import android.icu.text.TimeZoneFormat; @@ -29,6 +28,7 @@ import android.text.style.TtsSpan; import android.util.Log; import android.view.View; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.text.BidiFormatter; import androidx.core.text.TextDirectionHeuristicsCompat; diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java index 11fae24aa677..f07daa3add8b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java @@ -22,7 +22,6 @@ import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_USE import android.annotation.ColorInt; import android.annotation.DrawableRes; -import android.annotation.NonNull; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.res.ColorStateList; @@ -45,6 +44,7 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.os.UserHandle; +import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java index 0b3a519cd919..f32902329818 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java @@ -16,11 +16,11 @@ package com.android.settingslib.enterprise; -import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.Context; import android.content.DialogInterface; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settingslib.RestrictedLockUtils; diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java index 714accc09763..7e3395bd14f5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java @@ -16,7 +16,6 @@ package com.android.settingslib.enterprise; -import android.annotation.NonNull; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -24,6 +23,7 @@ import android.net.Uri; import android.provider.Settings; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settingslib.RestrictedLockUtils; diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java index 17c2b02ed576..71d58098cf75 100644 --- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java +++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java @@ -16,7 +16,6 @@ package com.android.settingslib.graph; -import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -35,6 +34,8 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.util.TypedValue; +import androidx.annotation.Nullable; + import com.android.settingslib.R; import com.android.settingslib.Utils; diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java index f01eb2a5606e..65d53f30bd2b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java +++ b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java @@ -16,7 +16,7 @@ package com.android.settingslib.graph; -import android.annotation.NonNull; + import android.content.Context; import android.content.res.Resources; import android.graphics.PorterDuff; @@ -25,6 +25,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.view.Gravity; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.settingslib.R; diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java index 4d0804e1bbb7..9a19f9368449 100644 --- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java +++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java @@ -16,8 +16,6 @@ package com.android.settingslib.graph; import android.animation.ArgbEvaluator; import android.annotation.IntRange; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Canvas; @@ -36,6 +34,9 @@ import android.telephony.CellSignalStrength; import android.util.LayoutDirection; import android.util.PathParser; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.settingslib.R; import com.android.settingslib.Utils; diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java index 1712a6b67e2f..318e3dd51f26 100644 --- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java @@ -16,8 +16,6 @@ package com.android.settingslib.inputmethod; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; @@ -30,6 +28,8 @@ import android.util.Log; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.preference.Preference; import androidx.preference.PreferenceFragment; import androidx.preference.PreferenceScreen; diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java index 78ec58b89800..a23164351cb7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java @@ -16,8 +16,6 @@ package com.android.settingslib.inputmethod; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.ContentResolver; import android.content.Context; @@ -32,6 +30,8 @@ import android.util.Log; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceScreen; diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java index 4384400eaa88..5c0c97974891 100644 --- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java @@ -17,7 +17,6 @@ package com.android.settingslib.inputmethod; import android.annotation.AnyThread; -import android.annotation.NonNull; import android.annotation.UiThread; import android.content.ContentResolver; import android.content.Context; @@ -26,6 +25,8 @@ import android.util.SparseArray; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; +import androidx.annotation.NonNull; + import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.DirectBootAwareness; diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 52b51d7e42e9..ba40a5030141 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -43,8 +43,6 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.TargetApi; import android.app.Notification; import android.bluetooth.BluetoothAdapter; @@ -59,6 +57,8 @@ import android.text.TextUtils; import android.util.Log; import androidx.annotation.DoNotInline; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.internal.annotations.VisibleForTesting; diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java index 0be2e0efaea2..d5b5af7a98c4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java @@ -16,8 +16,6 @@ package com.android.settingslib.media; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.Notification; import android.content.Context; import android.media.MediaRoute2Info; @@ -27,6 +25,9 @@ import android.media.RoutingSessionInfo; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.bluetooth.LocalBluetoothManager; diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 80eeab56cc45..0676ce5a65f1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -29,7 +29,6 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; import android.Manifest; -import android.annotation.NonNull; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; @@ -40,6 +39,7 @@ import android.media.MediaRoute2Info; import android.media.RouteListingPreference; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.settingslib.R; diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java index 5e9ac5a59091..bcfdebe74976 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java @@ -16,7 +16,6 @@ package com.android.settingslib.net; -import android.annotation.NonNull; import android.app.usage.NetworkStats; import android.app.usage.NetworkStatsManager; import android.content.Context; @@ -27,6 +26,7 @@ import android.text.format.DateUtils; import android.util.Pair; import android.util.Range; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.loader.content.AsyncTaskLoader; diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java index 7fb959ae4d76..dfa5ed12b7dd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java @@ -16,7 +16,6 @@ package com.android.settingslib.notification; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.AlertDialog; @@ -43,6 +42,8 @@ import android.widget.RadioGroup; import android.widget.ScrollView; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.R; diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java index fbf8a2f2cf4f..23b2cc2df794 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java +++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java @@ -16,8 +16,6 @@ package com.android.settingslib.volume; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -42,6 +40,9 @@ import android.os.Looper; import android.os.Message; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.io.PrintWriter; import java.util.HashMap; import java.util.HashSet; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 303ee3c9fca2..cf452314163f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -21,7 +21,6 @@ import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_ import android.annotation.IntDef; import android.annotation.MainThread; -import android.annotation.Nullable; import android.app.AppGlobals; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -58,6 +57,7 @@ import android.util.Pair; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.CollectionUtils; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java index 98272cc1dabd..e5085260b56f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -15,7 +15,6 @@ */ package com.android.settingslib.wifi; -import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; @@ -32,6 +31,7 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java index 7ffae4094add..c0ca12529897 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java @@ -18,12 +18,12 @@ package com.android.settingslib.wifi; import static android.os.UserManager.DISALLOW_CONFIG_WIFI; -import android.annotation.NonNull; import android.content.Context; import android.os.Bundle; import android.os.UserManager; import android.util.SparseArray; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import java.util.HashMap; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java index a0c2698da4b5..14967ecb6ee0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java @@ -22,7 +22,6 @@ import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; import android.annotation.AnyThread; -import android.annotation.NonNull; import android.annotation.TestApi; import android.content.BroadcastReceiver; import android.content.Context; @@ -34,6 +33,7 @@ import android.os.Process; import android.util.Log; import androidx.annotation.GuardedBy; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java index 8b5ea30e17fe..c83524462b15 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java @@ -18,8 +18,10 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -43,6 +45,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -79,6 +82,8 @@ public class HearingAidAudioRoutingHelperTest { when(mAudioDeviceInfo.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).thenReturn( new AudioDeviceInfo[]{mAudioDeviceInfo}); + doReturn(Collections.emptyList()).when(mAudioManager).getPreferredDevicesForStrategy( + any(AudioProductStrategy.class)); when(mAudioStrategy.getAudioAttributesForLegacyStreamType( AudioManager.STREAM_MUSIC)) .thenReturn((new AudioAttributes.Builder()).build()); @@ -92,7 +97,10 @@ public class HearingAidAudioRoutingHelperTest { } @Test - public void setPreferredDeviceRoutingStrategies_valueAuto_callRemoveStrategy() { + public void setPreferredDeviceRoutingStrategies_hadValueThenValueAuto_callRemoveStrategy() { + when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn( + mHearingDeviceAttribute); + mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy), mHearingDeviceAttribute, HearingAidAudioRoutingConstants.RoutingValue.AUTO); @@ -101,6 +109,17 @@ public class HearingAidAudioRoutingHelperTest { } @Test + public void setPreferredDeviceRoutingStrategies_NoValueThenValueAuto_notCallRemoveStrategy() { + when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(null); + + mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy), + mHearingDeviceAttribute, + HearingAidAudioRoutingConstants.RoutingValue.AUTO); + + verify(mAudioManager, never()).removePreferredDeviceForStrategy(mAudioStrategy); + } + + @Test public void setPreferredDeviceRoutingStrategies_valueHearingDevice_callSetStrategy() { mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy), mHearingDeviceAttribute, diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java index 5aee8cdb8b9e..194a0e29de34 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java @@ -27,9 +27,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.annotation.NonNull; import android.view.View; +import androidx.annotation.NonNull; import androidx.preference.PreferenceGroupAdapter; import androidx.preference.SwitchPreference; import androidx.recyclerview.widget.RecyclerView; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java index 1d5f1b20e31e..819d4b321b8c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java @@ -16,7 +16,7 @@ package com.android.settingslib.enterprise; -import android.annotation.Nullable; +import androidx.annotation.Nullable; class FakeDeviceAdminStringProvider implements DeviceAdminStringProvider { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java index c79440e58e17..77c46f7105a7 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.annotation.NonNull; import android.app.usage.NetworkStats; import android.app.usage.NetworkStatsManager; import android.content.Context; @@ -37,6 +36,8 @@ import android.net.NetworkTemplate; import android.text.format.DateUtils; import android.util.Range; +import androidx.annotation.NonNull; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java index fae3aeafd5fb..844880466ea1 100644 --- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java @@ -16,12 +16,13 @@ package com.android.settingslib.testutils.shadow; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.AttributionSource; import android.content.Context; import android.content.PermissionChecker; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java index dac8142732e5..fde378fc3e5e 100644 --- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java @@ -15,12 +15,13 @@ */ package com.android.settingslib.testutils.shadow; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; import android.media.RoutingSessionInfo; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp index adebdcdcf26a..d5814e3a9b79 100644 --- a/packages/SettingsProvider/Android.bp +++ b/packages/SettingsProvider/Android.bp @@ -59,10 +59,10 @@ android_test { // Note we statically link SettingsProviderLib to do some unit tests. It's not accessible otherwise // because this test is not an instrumentation test. (because the target runs in the system process.) "SettingsProviderLib", - "androidx.test.rules", "flag-junit", "junit", + "libaconfig_java_proto_lite", "mockito-target-minus-junit4", "platform-test-annotations", "truth", diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 8f459c647316..73c2e22240d3 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -18,6 +18,9 @@ package com.android.providers.settings; import static android.os.Process.FIRST_APPLICATION_UID; +import android.aconfig.Aconfig.flag_state; +import android.aconfig.Aconfig.parsed_flag; +import android.aconfig.Aconfig.parsed_flags; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -147,6 +150,17 @@ final class SettingsState { */ private static final String CONFIG_STAGED_PREFIX = "staged/"; + private static final List<String> sAconfigTextProtoFilesOnDevice = List.of( + "/system/etc/aconfig_flags.pb", + "/system_ext/etc/aconfig_flags.pb", + "/product/etc/aconfig_flags.pb", + "/vendor/etc/aconfig_flags.pb"); + + /** + * This tag is applied to all aconfig default value-loaded flags. + */ + private static final String BOOT_LOADED_DEFAULT_TAG = "BOOT_LOADED_DEFAULT"; + // This was used in version 120 and before. private static final String NULL_VALUE_OLD_STYLE = "null"; @@ -315,6 +329,59 @@ final class SettingsState { synchronized (mLock) { readStateSyncLocked(); + + if (Flags.loadAconfigDefaults()) { + // Only load aconfig defaults if this is the first boot, the XML + // file doesn't exist yet, or this device is on its first boot after + // an OTA. + boolean shouldLoadAconfigValues = isConfigSettingsKey(mKey) + && (!file.exists() + || mContext.getPackageManager().isDeviceUpgrading()); + if (shouldLoadAconfigValues) { + loadAconfigDefaultValuesLocked(); + } + } + } + } + + @GuardedBy("mLock") + private void loadAconfigDefaultValuesLocked() { + for (String fileName : sAconfigTextProtoFilesOnDevice) { + try (FileInputStream inputStream = new FileInputStream(fileName)) { + byte[] contents = inputStream.readAllBytes(); + loadAconfigDefaultValues(contents); + } catch (IOException e) { + Slog.e(LOG_TAG, "failed to read protobuf", e); + } + } + } + + @VisibleForTesting + @GuardedBy("mLock") + public void loadAconfigDefaultValues(byte[] fileContents) { + try { + parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents); + + if (parsedFlags == null) { + Slog.e(LOG_TAG, "failed to parse aconfig protobuf"); + return; + } + + for (parsed_flag flag : parsedFlags.getParsedFlagList()) { + String flagName = flag.getNamespace() + "/" + + flag.getPackage() + "." + flag.getName(); + String value = flag.getState() == flag_state.ENABLED ? "true" : "false"; + + Setting existingSetting = getSettingLocked(flagName); + boolean isDefaultLoaded = existingSetting.getTag() != null + && existingSetting.getTag().equals(BOOT_LOADED_DEFAULT_TAG); + if (existingSetting.getValue() == null || isDefaultLoaded) { + insertSettingLocked(flagName, value, BOOT_LOADED_DEFAULT_TAG, + false, flag.getPackage()); + } + } + } catch (IOException e) { + Slog.e(LOG_TAG, "failed to parse protobuf", e); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig index 27ce0d4c7252..ecac5ee18582 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig +++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig @@ -6,3 +6,11 @@ flag { description: "When enabled, allows setting and displaying local overrides via adb." bug: "298392357" } + +flag { + name: "load_aconfig_defaults" + namespace: "core_experiments_team_internal" + description: "When enabled, loads aconfig default values into DeviceConfig on boot." + bug: "311155098" + is_fixed_read_only: true +} diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index 02a7bc1646ba..24625eaa5e13 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -15,6 +15,9 @@ */ package com.android.providers.settings; +import android.aconfig.Aconfig; +import android.aconfig.Aconfig.parsed_flag; +import android.aconfig.Aconfig.parsed_flags; import android.os.Looper; import android.test.AndroidTestCase; import android.util.Xml; @@ -84,6 +87,86 @@ public class SettingsStateTest extends AndroidTestCase { super.tearDown(); } + public void testLoadValidAconfigProto() { + int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); + Object lock = new Object(); + SettingsState settingsState = new SettingsState( + getContext(), lock, mSettingsFile, configKey, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); + + parsed_flags flags = parsed_flags + .newBuilder() + .addParsedFlag(parsed_flag + .newBuilder() + .setPackage("com.android.flags") + .setName("flag1") + .setNamespace("test_namespace") + .setDescription("test flag") + .addBug("12345678") + .setState(Aconfig.flag_state.DISABLED) + .setPermission(Aconfig.flag_permission.READ_WRITE)) + .addParsedFlag(parsed_flag + .newBuilder() + .setPackage("com.android.flags") + .setName("flag2") + .setNamespace("test_namespace") + .setDescription("another test flag") + .addBug("12345678") + .setState(Aconfig.flag_state.ENABLED) + .setPermission(Aconfig.flag_permission.READ_WRITE)) + .build(); + + synchronized (lock) { + settingsState.loadAconfigDefaultValues(flags.toByteArray()); + settingsState.persistSettingsLocked(); + } + settingsState.waitForHandler(); + + synchronized (lock) { + assertEquals("false", + settingsState.getSettingLocked( + "test_namespace/com.android.flags.flag1").getValue()); + assertEquals("true", + settingsState.getSettingLocked( + "test_namespace/com.android.flags.flag2").getValue()); + } + } + + public void testSkipLoadingAconfigFlagWithMissingFields() { + int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); + Object lock = new Object(); + SettingsState settingsState = new SettingsState( + getContext(), lock, mSettingsFile, configKey, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); + + parsed_flags flags = parsed_flags + .newBuilder() + .addParsedFlag(parsed_flag + .newBuilder() + .setDescription("test flag") + .addBug("12345678") + .setState(Aconfig.flag_state.DISABLED) + .setPermission(Aconfig.flag_permission.READ_WRITE)) + .build(); + + synchronized (lock) { + settingsState.loadAconfigDefaultValues(flags.toByteArray()); + settingsState.persistSettingsLocked(); + } + settingsState.waitForHandler(); + + synchronized (lock) { + assertEquals(null, + settingsState.getSettingLocked( + "test_namespace/com.android.flags.flag1").getValue()); + } + } + + public void testInvalidAconfigProtoDoesNotCrash() { + SettingsState settingsState = getSettingStateObject(); + settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes()); + } + public void testIsBinary() { assertFalse(SettingsState.isBinary(" abc 日本語")); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 6e65c16c41a1..477c42e01fba 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -688,6 +688,9 @@ <!-- Permission required for CTS test - CtsAmbientContextDetectionServiceDeviceTest --> <uses-permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" /> + <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases --> + <uses-permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE" /> + <!-- Permission required for CTS test - CallAudioInterceptionTest --> <uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 1a35f04b393f..a03fa9b39bfc 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -256,6 +256,9 @@ <!-- launcher apps --> <uses-permission android:name="android.permission.ACCESS_SHORTCUTS" /> + <!-- Permission to start Launcher's widget picker activity. --> + <uses-permission android:name="android.permission.START_WIDGET_PICKER_ACTIVITY" /> + <uses-permission android:name="android.permission.MODIFY_THEME_OVERLAY" /> <!-- accessibility --> @@ -974,15 +977,6 @@ android:excludeFromRecents="true" android:visibleToInstantApps="true"/> - <activity android:name="com.android.systemui.communal.widgets.WidgetPickerActivity" - android:theme="@style/Theme.EditWidgetsActivity" - android:excludeFromRecents="true" - android:autoRemoveFromRecents="true" - android:showOnLockScreen="true" - android:launchMode="singleTop" - android:exported="false"> - </activity> - <activity android:name="com.android.systemui.communal.widgets.EditWidgetsActivity" android:theme="@style/Theme.EditWidgetsActivity" android:excludeFromRecents="true" diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index ab4fe76a58c7..98a2d9ff2d19 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -45,6 +45,13 @@ flag { } flag { + name: "notifications_improved_hun_animation" + namespace: "systemui" + description: "Adds a translateY animation, and other improvements to match the motion specs of the HUN Intro + Outro animations." + bug: "243302608" +} + +flag { name: "notification_lifetime_extension_refactor" namespace: "systemui" description: "Enables moving notification lifetime extension management from SystemUI to " @@ -241,6 +248,13 @@ flag { } flag { + name: "switch_user_on_bg" + namespace: "systemui" + description: "Does user switching on a background thread" + bug: "284095720" +} + +flag { name: "status_bar_static_inout_indicators" namespace: "systemui" description: "(Upstream request) Always show the network activity inout indicators and " @@ -248,3 +262,23 @@ flag { bug: "310715220" } +flag { + name: "haptic_volume_slider" + namespace: "systemui" + description: "Adds haptic feedback to the volume slider." + bug: "316953430" +} + +flag { + name: "screenshare_notification_hiding" + namespace: "systemui" + description: "Enable hiding of notifications during screenshare" + bug: "312784809" +} + +flag { + name: "bluetooth_qs_tile_dialog_auto_on_toggle" + namespace: "systemui" + description: "Displays the auto on toggle in the bluetooth QS tile dialog" + bug: "316985153" +} diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/core/Android.bp index 8438051e3430..8438051e3430 100644 --- a/packages/SystemUI/animation/Android.bp +++ b/packages/SystemUI/animation/core/Android.bp diff --git a/packages/SystemUI/animation/AndroidManifest.xml b/packages/SystemUI/animation/core/AndroidManifest.xml index 321cc53142c6..321cc53142c6 100644 --- a/packages/SystemUI/animation/AndroidManifest.xml +++ b/packages/SystemUI/animation/core/AndroidManifest.xml diff --git a/packages/SystemUI/animation/build.gradle b/packages/SystemUI/animation/core/build.gradle index 939455fa44ac..12637f4071df 100644 --- a/packages/SystemUI/animation/build.gradle +++ b/packages/SystemUI/animation/core/build.gradle @@ -5,8 +5,8 @@ apply plugin: 'kotlin-android' android { sourceSets { main { - java.srcDirs = ["${SYS_UI_DIR}/animation/src/com/android/systemui/surfaceeffects/"] - manifest.srcFile "${SYS_UI_DIR}/animation/AndroidManifest.xml" + java.srcDirs = ["${SYS_UI_DIR}/animation/core/src/com/android/systemui/surfaceeffects/"] + manifest.srcFile "${SYS_UI_DIR}/animation/core/AndroidManifest.xml" } } diff --git a/packages/SystemUI/animation/res/anim/launch_dialog_enter.xml b/packages/SystemUI/animation/core/res/anim/launch_dialog_enter.xml index c6b87d38f7da..c6b87d38f7da 100644 --- a/packages/SystemUI/animation/res/anim/launch_dialog_enter.xml +++ b/packages/SystemUI/animation/core/res/anim/launch_dialog_enter.xml diff --git a/packages/SystemUI/animation/res/anim/launch_dialog_exit.xml b/packages/SystemUI/animation/core/res/anim/launch_dialog_exit.xml index a0f441eaeed4..a0f441eaeed4 100644 --- a/packages/SystemUI/animation/res/anim/launch_dialog_exit.xml +++ b/packages/SystemUI/animation/core/res/anim/launch_dialog_exit.xml diff --git a/packages/SystemUI/animation/res/values/ids.xml b/packages/SystemUI/animation/core/res/values/ids.xml index 2d82307aca76..2d82307aca76 100644 --- a/packages/SystemUI/animation/res/values/ids.xml +++ b/packages/SystemUI/animation/core/res/values/ids.xml diff --git a/packages/SystemUI/animation/res/values/styles.xml b/packages/SystemUI/animation/core/res/values/styles.xml index 3b3f7f6128fa..3b3f7f6128fa 100644 --- a/packages/SystemUI/animation/res/values/styles.xml +++ b/packages/SystemUI/animation/core/res/values/styles.xml diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 9c46ebdc5ac8..9c46ebdc5ac8 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ActivityLaunchAnimator.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/AnimationFeatureFlags.kt index 1c9dabbb0e07..1c9dabbb0e07 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/AnimationFeatureFlags.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt index b879ba0b1ece..b879ba0b1ece 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 168039ed5d3d..168039ed5d3d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/DialogLaunchAnimator.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/Expandable.kt index c49a487c6766..c49a487c6766 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/Expandable.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontInterpolator.kt index addabcc0282a..addabcc0282a 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontInterpolator.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontVariationUtils.kt index 78ae4af258fc..78ae4af258fc 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontVariationUtils.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index b738e2bc972b..b738e2bc972b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchAnimator.kt index d6eba2e7064d..d6eba2e7064d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchAnimator.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchableView.kt index ed8e70568b48..ed8e70568b48 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchableView.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/RemoteAnimationDelegate.kt index d465962d6edf..d465962d6edf 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/RemoteAnimationDelegate.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ShadeInterpolation.kt index b89a8b0e0272..b89a8b0e0272 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ShadeInterpolation.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt index 8dc74951d332..dc54240affc3 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt @@ -42,9 +42,9 @@ interface TypefaceVariantCache { return baseTypeface } - val axes = FontVariationAxis.fromFontVariationSettings(fVar) - ?.toMutableList() - ?: mutableListOf() + val axes = + FontVariationAxis.fromFontVariationSettings(fVar)?.toMutableList() + ?: mutableListOf() axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) } if (axes.isEmpty()) { return baseTypeface @@ -206,12 +206,14 @@ class TextAnimator( * * Here is an example of font runs: "fin. 終わり" * + * ``` * Characters : f i n . _ 終 わ り * Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A * Font Runs : <-- Roboto-Regular.ttf --><-- NotoSans-CJK.otf --> * runStart = 0, runLength = 5 runStart = 5, runLength = 3 * Glyph IDs : 194 48 7 8 4367 1039 1002 * Glyph Index: 0 1 2 3 0 1 2 + * ``` * * In this example, the "fi" is converted into ligature form, thus the single glyph ID is * assigned for two characters, f and i. @@ -243,22 +245,21 @@ class TextAnimator( /** * Set text style with animation. * - * By passing -1 to weight, the view preserve the current weight. - * By passing -1 to textSize, the view preserve the current text size. - * Bu passing -1 to duration, the default text animation, 1000ms, is used. - * By passing false to animate, the text will be updated without animation. + * By passing -1 to weight, the view preserve the current weight. By passing -1 to textSize, the + * view preserve the current text size. Bu passing -1 to duration, the default text animation, + * 1000ms, is used. By passing false to animate, the text will be updated without animation. * * @param fvar an optional text fontVariationSettings. * @param textSize an optional font size. - * @param colors an optional colors array that must be the same size as numLines passed to - * the TextInterpolator + * @param colors an optional colors array that must be the same size as numLines passed to the + * TextInterpolator * @param strokeWidth an optional paint stroke width * @param animate an optional boolean indicating true for showing style transition as animation, - * false for immediate style transition. True by default. + * false for immediate style transition. True by default. * @param duration an optional animation duration in milliseconds. This is ignored if animate is - * false. + * false. * @param interpolator an optional time interpolator. If null is passed, last set interpolator - * will be used. This is ignored if animate is false. + * will be used. This is ignored if animate is false. */ fun setTextStyle( fvar: String? = "", @@ -324,7 +325,8 @@ class TextAnimator( } /** - * Set text style with animation. Similar as + * Set text style with animation. Similar as fun setTextStyle( fvar: String? = "", textSize: + * ``` * fun setTextStyle( * fvar: String? = "", * textSize: Float = -1f, @@ -336,6 +338,7 @@ class TextAnimator( * delay: Long = 0, * onAnimationEnd: Runnable? = null * ) + * ``` * * @param weight an optional style value for `wght` in fontVariationSettings. * @param width an optional style value for `wdth` in fontVariationSettings. @@ -356,12 +359,13 @@ class TextAnimator( delay: Long = 0, onAnimationEnd: Runnable? = null ) { - val fvar = fontVariationUtils.updateFontVariation( - weight = weight, - width = width, - opticalSize = opticalSize, - roundness = roundness, - ) + val fvar = + fontVariationUtils.updateFontVariation( + weight = weight, + width = width, + opticalSize = opticalSize, + roundness = roundness, + ) setTextStyle( fvar = fvar, textSize = textSize, diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextInterpolator.kt index 02caeeddd774..02caeeddd774 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextInterpolator.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt index 1290f0097536..1290f0097536 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt index 00d905652987..a9c53d190cea 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt @@ -196,9 +196,9 @@ class ViewHierarchyAnimator { * * @param includeFadeIn true if the animator should also fade in the view and child views. * @param fadeInInterpolator the interpolator to use when fading in the view. Unused if - * [includeFadeIn] is false. - * @param onAnimationEnd an optional runnable that will be run once the animation - * finishes successfully. Will not be run if the animation is cancelled. + * [includeFadeIn] is false. + * @param onAnimationEnd an optional runnable that will be run once the animation finishes + * successfully. Will not be run if the animation is cancelled. */ @JvmOverloads fun animateAddition( @@ -241,7 +241,10 @@ class ViewHierarchyAnimator { // First, fade in the container view val containerDuration = duration / 6 createAndStartFadeInAnimator( - rootView, containerDuration, startDelay = 0, interpolator = fadeInInterpolator + rootView, + containerDuration, + startDelay = 0, + interpolator = fadeInInterpolator ) // Then, fade in the child views @@ -397,7 +400,7 @@ class ViewHierarchyAnimator { * included by specifying [includeMargins]. * * @param onAnimationEnd an optional runnable that will be run once the animation finishes - * successfully. Will not be run if the animation is cancelled. + * successfully. Will not be run if the animation is cancelled. */ @JvmOverloads fun animateRemoval( @@ -616,6 +619,7 @@ class ViewHierarchyAnimator { * not newly introduced margins are included. * * Base case + * * ``` * 1) origin=TOP * x---------x x---------x x---------x x---------x x---------x @@ -636,9 +640,11 @@ class ViewHierarchyAnimator { * x-----x x-------x | | * x---------x * ``` + * * In case the start and end values differ in the direction of the origin, and * [ignorePreviousValues] is false, the previous values are used and a translation is * included in addition to the view expansion. + * * ``` * origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70) * x @@ -777,8 +783,7 @@ class ViewHierarchyAnimator { includeMargins: Boolean = false, ): Map<Bound, Int> { val marginAdjustment = - if (includeMargins && - (rootView.layoutParams is ViewGroup.MarginLayoutParams)) { + if (includeMargins && (rootView.layoutParams is ViewGroup.MarginLayoutParams)) { val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams DimenHolder( left = marginLp.leftMargin, @@ -786,9 +791,9 @@ class ViewHierarchyAnimator { right = marginLp.rightMargin, bottom = marginLp.bottomMargin ) - } else { - DimenHolder(0, 0, 0, 0) - } + } else { + DimenHolder(0, 0, 0, 0) + } // These are the end values to use *if* this bound is part of the destination. val endLeft = left - marginAdjustment.left @@ -805,60 +810,69 @@ class ViewHierarchyAnimator { // - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight. return when (destination) { - Hotspot.TOP -> mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.LEFT to left, - Bound.RIGHT to right, - ) - Hotspot.TOP_RIGHT -> mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - ) - Hotspot.RIGHT -> mapOf( - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - Bound.TOP to top, - Bound.BOTTOM to bottom, - ) - Hotspot.BOTTOM_RIGHT -> mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - ) - Hotspot.BOTTOM -> mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.LEFT to left, - Bound.RIGHT to right, - ) - Hotspot.BOTTOM_LEFT -> mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - ) - Hotspot.LEFT -> mapOf( - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - Bound.TOP to top, - Bound.BOTTOM to bottom, - ) - Hotspot.TOP_LEFT -> mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - ) - Hotspot.CENTER -> mapOf( - Bound.LEFT to (endLeft + endRight) / 2, - Bound.RIGHT to (endLeft + endRight) / 2, - Bound.TOP to (endTop + endBottom) / 2, - Bound.BOTTOM to (endTop + endBottom) / 2, - ) + Hotspot.TOP -> + mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.LEFT to left, + Bound.RIGHT to right, + ) + Hotspot.TOP_RIGHT -> + mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + ) + Hotspot.RIGHT -> + mapOf( + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + Bound.TOP to top, + Bound.BOTTOM to bottom, + ) + Hotspot.BOTTOM_RIGHT -> + mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + ) + Hotspot.BOTTOM -> + mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.LEFT to left, + Bound.RIGHT to right, + ) + Hotspot.BOTTOM_LEFT -> + mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + ) + Hotspot.LEFT -> + mapOf( + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + Bound.TOP to top, + Bound.BOTTOM to bottom, + ) + Hotspot.TOP_LEFT -> + mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + ) + Hotspot.CENTER -> + mapOf( + Bound.LEFT to (endLeft + endRight) / 2, + Bound.RIGHT to (endLeft + endRight) / 2, + Bound.TOP to (endTop + endBottom) / 2, + Bound.BOTTOM to (endTop + endBottom) / 2, + ) } } @@ -1083,11 +1097,13 @@ class ViewHierarchyAnimator { animator.startDelay = startDelay animator.duration = duration animator.interpolator = interpolator - animator.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - view.setTag(R.id.tag_alpha_animator, null /* tag */) + animator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + view.setTag(R.id.tag_alpha_animator, null /* tag */) + } } - }) + ) (view.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.cancel() view.setTag(R.id.tag_alpha_animator, animator) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewRootSync.kt index e4f6db57f689..e4f6db57f689 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewRootSync.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpec.kt index dd32851a97cf..dd32851a97cf 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpec.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt index c6b70736bc67..c6b70736bc67 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackTransformation.kt index 49d1fb423d2b..49d1fb423d2b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackTransformation.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt index 8740d1467296..8740d1467296 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt index 7538f188fb81..7538f188fb81 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableImageView.kt index e42b589f05cf..e42b589f05cf 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableImageView.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt index bce262291f87..bce262291f87 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableTextView.kt index 147669528c5e..147669528c5e 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableTextView.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt index d8e17c9c8204..d8e17c9c8204 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt index 6c175ddf1ea4..6c175ddf1ea4 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt index d4372507e2c4..d4372507e2c4 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt index 91c0a5b635c9..91c0a5b635c9 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt index 7e56f4b3d2b2..7e56f4b3d2b2 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt index b89912709576..b89912709576 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt index c94fad7246fa..c94fad7246fa 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt index df07856e325e..df07856e325e 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt index 78898932249b..78898932249b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt index 23fcb691ddb4..23fcb691ddb4 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt index 2cd587ffbc45..2cd587ffbc45 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt index b8f4b276dd6a..b8f4b276dd6a 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt index d3c57c91405a..d3c57c91405a 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt index 43d6504fce84..43d6504fce84 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/AnimatorExtensions.kt b/packages/SystemUI/animation/core/src/com/android/systemui/util/AnimatorExtensions.kt index 35dbb89ad801..35dbb89ad801 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/util/AnimatorExtensions.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/util/AnimatorExtensions.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt b/packages/SystemUI/animation/core/src/com/android/systemui/util/Dialog.kt index 0f63548b6f0c..0f63548b6f0c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/util/Dialog.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt b/packages/SystemUI/animation/core/src/com/android/systemui/util/Dimension.kt index 4bc9972dd50f..4bc9972dd50f 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt +++ b/packages/SystemUI/animation/core/src/com/android/systemui/util/Dimension.kt diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt index 4d53cca66e5b..cbf249653577 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardViewConfigurator import com.android.systemui.keyguard.qualifiers.KeyguardRootView import com.android.systemui.keyguard.ui.composable.LockscreenScene +import com.android.systemui.keyguard.ui.composable.LockscreenSceneBlueprintModule import com.android.systemui.scene.shared.model.Scene import dagger.Binds import dagger.Module @@ -29,7 +30,12 @@ import dagger.multibindings.IntoSet import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi -@Module +@Module( + includes = + [ + LockscreenSceneBlueprintModule::class, + ], +) interface LockscreenSceneModule { @Binds @IntoSet fun lockscreenScene(scene: LockscreenScene): Scene diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt new file mode 100644 index 000000000000..2cb00342cba5 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt @@ -0,0 +1,73 @@ +/* + * 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.keyguard.ui.composable + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.SceneTransitionLayout +import com.android.compose.animation.scene.transitions +import com.android.systemui.keyguard.ui.composable.blueprint.LockscreenSceneBlueprint +import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel +import javax.inject.Inject + +/** + * Renders the content of the lockscreen. + * + * This is separate from the [LockscreenScene] because it's meant to support usage of this UI from + * outside the scene container framework. + */ +class LockscreenContent +@Inject +constructor( + private val viewModel: LockscreenContentViewModel, + private val blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>, +) { + + private val sceneKeyByBlueprint: Map<LockscreenSceneBlueprint, SceneKey> by lazy { + blueprints.associateWith { blueprint -> SceneKey(blueprint.id) } + } + private val sceneKeyByBlueprintId: Map<String, SceneKey> by lazy { + sceneKeyByBlueprint.entries.associate { (blueprint, sceneKey) -> blueprint.id to sceneKey } + } + + @Composable + fun Content( + modifier: Modifier = Modifier, + ) { + val coroutineScope = rememberCoroutineScope() + val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState() + + // Switch smoothly between blueprints, any composable tagged with element() will be + // transition-animated between any two blueprints, if they both display the same element. + SceneTransitionLayout( + currentScene = checkNotNull(sceneKeyByBlueprintId[blueprintId]), + onChangeScene = {}, + transitions = + transitions { sceneKeyByBlueprintId.values.forEach { sceneKey -> to(sceneKey) } }, + modifier = modifier, + ) { + sceneKeyByBlueprint.entries.forEach { (blueprint, sceneKey) -> + scene(sceneKey) { with(blueprint) { Content(Modifier.fillMaxSize()) } } + } + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index 30536547ae5c..93f31ec8f7ed 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class) +@file:OptIn(ExperimentalFoundationApi::class) package com.android.systemui.keyguard.ui.composable @@ -50,14 +50,17 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +/** Set this to `true` to use the LockscreenContent replacement of KeyguardRootView. */ +private val UseLockscreenContent = false + /** The lock screen scene shows when the device is locked. */ @SysUISingleton class LockscreenScene @@ -66,6 +69,7 @@ constructor( @Application private val applicationScope: CoroutineScope, private val viewModel: LockscreenSceneViewModel, @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View, + private val lockscreenContent: Lazy<LockscreenContent>, ) : ComposableScene { override val key = SceneKey.Lockscreen @@ -91,6 +95,7 @@ constructor( LockscreenScene( viewProvider = viewProvider, viewModel = viewModel, + lockscreenContent = lockscreenContent, modifier = modifier, ) } @@ -113,6 +118,7 @@ constructor( private fun SceneScope.LockscreenScene( viewProvider: () -> View, viewModel: LockscreenSceneViewModel, + lockscreenContent: Lazy<LockscreenContent>, modifier: Modifier = Modifier, ) { fun findSettingsMenu(): View { @@ -133,16 +139,24 @@ private fun SceneScope.LockscreenScene( modifier = Modifier.fillMaxSize(), ) - AndroidView( - factory = { _ -> - val keyguardRootView = viewProvider() - // Remove the KeyguardRootView from any parent it might already have in legacy code - // just in case (a view can't have two parents). - (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView) - keyguardRootView - }, - modifier = Modifier.fillMaxSize(), - ) + if (UseLockscreenContent) { + lockscreenContent + .get() + .Content( + modifier = Modifier.fillMaxSize(), + ) + } else { + AndroidView( + factory = { _ -> + val keyguardRootView = viewProvider() + // Remove the KeyguardRootView from any parent it might already have in legacy + // code just in case (a view can't have two parents). + (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView) + keyguardRootView + }, + modifier = Modifier.fillMaxSize(), + ) + } val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt new file mode 100644 index 000000000000..9abb50c35ccf --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt @@ -0,0 +1,34 @@ +/* + * 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.keyguard.ui.composable + +import com.android.systemui.keyguard.ui.composable.blueprint.CommunalBlueprintModule +import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule +import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule +import com.android.systemui.keyguard.ui.composable.blueprint.SplitShadeBlueprintModule +import dagger.Module + +@Module( + includes = + [ + CommunalBlueprintModule::class, + DefaultBlueprintModule::class, + ShortcutsBesideUdfpsBlueprintModule::class, + SplitShadeBlueprintModule::class, + ], +) +interface LockscreenSceneBlueprintModule diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt new file mode 100644 index 000000000000..7eddaaf9ba4b --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt @@ -0,0 +1,52 @@ +/* + * 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.keyguard.ui.composable.blueprint + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +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 dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet +import javax.inject.Inject + +/** Renders the lockscreen scene when showing the communal glanceable hub. */ +class CommunalBlueprint @Inject constructor() : LockscreenSceneBlueprint { + + override val id: String = "communal" + + @Composable + override fun SceneScope.Content(modifier: Modifier) { + Box(modifier.background(Color.Black)) { + Text( + text = "TODO(b/316211368): communal blueprint", + color = Color.White, + modifier = Modifier.align(Alignment.Center), + ) + } + } +} + +@Module +interface CommunalBlueprintModule { + @Binds @IntoSet fun blueprint(blueprint: CommunalBlueprint): LockscreenSceneBlueprint +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt new file mode 100644 index 000000000000..fc1df848d46f --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -0,0 +1,114 @@ +/* + * 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.keyguard.ui.composable.blueprint + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.IntOffset +import com.android.compose.animation.scene.SceneScope +import com.android.compose.modifiers.height +import com.android.compose.modifiers.width +import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection +import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection +import com.android.systemui.keyguard.ui.composable.section.ClockSection +import com.android.systemui.keyguard.ui.composable.section.LockSection +import com.android.systemui.keyguard.ui.composable.section.NotificationSection +import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection +import com.android.systemui.keyguard.ui.composable.section.StatusBarSection +import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet +import javax.inject.Inject + +/** + * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form + * factor). + */ +class DefaultBlueprint +@Inject +constructor( + private val viewModel: LockscreenContentViewModel, + private val statusBarSection: StatusBarSection, + private val clockSection: ClockSection, + private val smartSpaceSection: SmartSpaceSection, + private val notificationSection: NotificationSection, + private val lockSection: LockSection, + private val ambientIndicationSection: AmbientIndicationSection, + private val bottomAreaSection: BottomAreaSection, +) : LockscreenSceneBlueprint { + + override val id: String = "default" + + @Composable + override fun SceneScope.Content(modifier: Modifier) { + val context = LocalContext.current + val lockIconBounds = lockSection.lockIconBounds(context) + val isUdfpsVisible = viewModel.isUdfpsVisible + + Box( + modifier = modifier, + ) { + Column( + modifier = Modifier.fillMaxWidth().height { lockIconBounds.top }, + ) { + with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) } + with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) } + with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) } + with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) } + with(notificationSection) { + Notifications(modifier = Modifier.fillMaxWidth().weight(1f)) + } + if (!isUdfpsVisible) { + with(ambientIndicationSection) { + AmbientIndication(modifier = Modifier.fillMaxWidth()) + } + } + } + + with(lockSection) { + LockIcon( + modifier = + Modifier.width { lockIconBounds.width() } + .height { lockIconBounds.height() } + .offset { IntOffset(lockIconBounds.left, lockIconBounds.top) } + ) + } + + Column(modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) { + if (isUdfpsVisible) { + with(ambientIndicationSection) { + AmbientIndication(modifier = Modifier.fillMaxWidth()) + } + } + + with(bottomAreaSection) { BottomArea(modifier = Modifier.fillMaxWidth()) } + } + } + } +} + +@Module +interface DefaultBlueprintModule { + @Binds @IntoSet fun blueprint(blueprint: DefaultBlueprint): LockscreenSceneBlueprint +} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt index e6d38668335a..6d9cba4acb39 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt @@ -13,15 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.test; -import android.content.Context; +package com.android.systemui.keyguard.ui.composable.blueprint -import junit.framework.TestCase; +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.SceneScope -public class AndroidTestCase extends TestCase { - protected Context mContext; - public Context getContext() { - throw new RuntimeException("[ravenwood] Class Context is not supported yet."); - } +/** Defines interface for classes that can render the content for a specific blueprint/layout. */ +interface LockscreenSceneBlueprint { + + /** The ID that uniquely identifies this blueprint across all other blueprints. */ + val id: String + + /** Renders the content of this blueprint. */ + @Composable fun SceneScope.Content(modifier: Modifier) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt new file mode 100644 index 000000000000..fa913f1695fe --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt @@ -0,0 +1,173 @@ +/* + * 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.keyguard.ui.composable.blueprint + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.IntOffset +import com.android.compose.animation.scene.SceneScope +import com.android.compose.modifiers.height +import com.android.compose.modifiers.width +import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection +import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection +import com.android.systemui.keyguard.ui.composable.section.ClockSection +import com.android.systemui.keyguard.ui.composable.section.LockSection +import com.android.systemui.keyguard.ui.composable.section.NotificationSection +import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection +import com.android.systemui.keyguard.ui.composable.section.StatusBarSection +import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel +import com.android.systemui.res.R +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet +import javax.inject.Inject +import kotlin.math.roundToInt + +/** + * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form + * factor). + */ +class ShortcutsBesideUdfpsBlueprint +@Inject +constructor( + private val viewModel: LockscreenContentViewModel, + private val statusBarSection: StatusBarSection, + private val clockSection: ClockSection, + private val smartSpaceSection: SmartSpaceSection, + private val notificationSection: NotificationSection, + private val lockSection: LockSection, + private val ambientIndicationSection: AmbientIndicationSection, + private val bottomAreaSection: BottomAreaSection, +) : LockscreenSceneBlueprint { + + override val id: String = "shortcuts-besides-udfps" + + @Composable + override fun SceneScope.Content(modifier: Modifier) { + val context = LocalContext.current + val lockIconBounds = lockSection.lockIconBounds(context) + val isUdfpsVisible = viewModel.isUdfpsVisible + + Box( + modifier = modifier, + ) { + Column( + modifier = Modifier.fillMaxWidth().height { lockIconBounds.top }, + ) { + with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) } + with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) } + with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) } + with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) } + with(notificationSection) { + Notifications(modifier = Modifier.fillMaxWidth().weight(1f)) + } + if (!isUdfpsVisible) { + with(ambientIndicationSection) { + AmbientIndication(modifier = Modifier.fillMaxWidth()) + } + } + } + + val shortcutSizePx = + with(LocalDensity.current) { bottomAreaSection.shortcutSizeDp().toSize() } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier.fillMaxWidth().offset { + val rowTop = + if (shortcutSizePx.height > lockIconBounds.height()) { + (lockIconBounds.top - + (shortcutSizePx.height + lockIconBounds.height()) / 2) + .roundToInt() + } else { + lockIconBounds.top + } + + IntOffset(0, rowTop) + }, + ) { + Spacer(Modifier.weight(1f)) + + with(bottomAreaSection) { Shortcut(isStart = true) } + + Spacer(Modifier.weight(1f)) + + with(lockSection) { + LockIcon( + modifier = + Modifier.width { lockIconBounds.width() } + .height { lockIconBounds.height() } + ) + } + + Spacer(Modifier.weight(1f)) + + with(bottomAreaSection) { Shortcut(isStart = false) } + + Spacer(Modifier.weight(1f)) + } + + Column(modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) { + if (isUdfpsVisible) { + with(ambientIndicationSection) { + AmbientIndication(modifier = Modifier.fillMaxWidth()) + } + } + + with(bottomAreaSection) { + IndicationArea( + modifier = + Modifier.fillMaxWidth() + .padding( + horizontal = + dimensionResource( + R.dimen.keyguard_affordance_horizontal_offset + ) + ) + .padding( + bottom = + dimensionResource( + R.dimen.keyguard_affordance_vertical_offset + ) + ) + .heightIn(min = shortcutSizeDp().height), + ) + } + } + } + } +} + +@Module +interface ShortcutsBesideUdfpsBlueprintModule { + @Binds + @IntoSet + fun blueprint(blueprint: ShortcutsBesideUdfpsBlueprint): LockscreenSceneBlueprint +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt new file mode 100644 index 000000000000..7545d5fcc2b3 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt @@ -0,0 +1,55 @@ +/* + * 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.keyguard.ui.composable.blueprint + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +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 dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet +import javax.inject.Inject + +/** + * Renders the lockscreen scene when showing with a split shade (e.g. unfolded foldable and/or + * tablet form factor). + */ +class SplitShadeBlueprint @Inject constructor() : LockscreenSceneBlueprint { + + override val id: String = "split-shade" + + @Composable + override fun SceneScope.Content(modifier: Modifier) { + Box(modifier.background(Color.Black)) { + Text( + text = "TODO(b/316211368): split shade blueprint", + color = Color.White, + modifier = Modifier.align(Alignment.Center), + ) + } + } +} + +@Module +interface SplitShadeBlueprintModule { + @Binds @IntoSet fun blueprint(blueprint: SplitShadeBlueprint): LockscreenSceneBlueprint +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt new file mode 100644 index 000000000000..0e7ac5ec046a --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt @@ -0,0 +1,51 @@ +/* + * 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.keyguard.ui.composable.section + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +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.ElementKey +import com.android.compose.animation.scene.SceneScope +import javax.inject.Inject + +class AmbientIndicationSection @Inject constructor() { + @Composable + fun SceneScope.AmbientIndication(modifier: Modifier = Modifier) { + MovableElement( + key = AmbientIndicationElementKey, + modifier = modifier, + ) { + Box( + modifier = Modifier.fillMaxWidth().background(Color.Green), + ) { + Text( + text = "TODO(b/316211368): Ambient indication", + color = Color.White, + modifier = Modifier.align(Alignment.Center), + ) + } + } + } +} + +private val AmbientIndicationElementKey = ElementKey("AmbientIndication") diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt new file mode 100644 index 000000000000..53e4be34dcfa --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt @@ -0,0 +1,225 @@ +/* + * 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.keyguard.ui.composable.section + +import android.view.View +import android.widget.ImageView +import androidx.annotation.IdRes +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.res.ResourcesCompat +import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.SceneScope +import com.android.systemui.animation.view.LaunchableImageView +import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder +import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder +import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea +import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R +import com.android.systemui.statusbar.KeyguardIndicationController +import com.android.systemui.statusbar.VibratorHelper +import javax.inject.Inject +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.flow.Flow + +class BottomAreaSection +@Inject +constructor( + private val viewModel: KeyguardQuickAffordancesCombinedViewModel, + private val falsingManager: FalsingManager, + private val vibratorHelper: VibratorHelper, + private val indicationController: KeyguardIndicationController, + private val indicationAreaViewModel: KeyguardIndicationAreaViewModel, + private val keyguardRootViewModel: KeyguardRootViewModel, +) { + @Composable + fun SceneScope.BottomArea( + modifier: Modifier = Modifier, + ) { + Row( + modifier = + modifier + .padding( + horizontal = + dimensionResource(R.dimen.keyguard_affordance_horizontal_offset) + ) + .padding( + bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset) + ), + ) { + Shortcut( + isStart = true, + ) + + IndicationArea( + modifier = Modifier.weight(1f), + ) + + Shortcut( + isStart = false, + ) + } + } + + @Composable + fun SceneScope.Shortcut( + isStart: Boolean, + modifier: Modifier = Modifier, + ) { + MovableElement( + key = if (isStart) StartButtonElementKey else EndButtonElementKey, + modifier = modifier, + ) { + Shortcut( + viewId = if (isStart) R.id.start_button else R.id.end_button, + viewModel = if (isStart) viewModel.startButton else viewModel.endButton, + transitionAlpha = viewModel.transitionAlpha, + falsingManager = falsingManager, + vibratorHelper = vibratorHelper, + indicationController = indicationController, + ) + } + } + + @Composable + fun SceneScope.IndicationArea( + modifier: Modifier = Modifier, + ) { + MovableElement( + key = IndicationAreaElementKey, + modifier = modifier, + ) { + IndicationArea( + indicationAreaViewModel = indicationAreaViewModel, + keyguardRootViewModel = keyguardRootViewModel, + indicationController = indicationController, + ) + } + } + + @Composable + fun shortcutSizeDp(): DpSize { + return DpSize( + width = dimensionResource(R.dimen.keyguard_affordance_fixed_width), + height = dimensionResource(R.dimen.keyguard_affordance_fixed_height), + ) + } + + @Composable + private fun Shortcut( + @IdRes viewId: Int, + viewModel: Flow<KeyguardQuickAffordanceViewModel>, + transitionAlpha: Flow<Float>, + falsingManager: FalsingManager, + vibratorHelper: VibratorHelper, + indicationController: KeyguardIndicationController, + modifier: Modifier = Modifier, + ) { + val (binding, setBinding) = mutableStateOf<KeyguardQuickAffordanceViewBinder.Binding?>(null) + + AndroidView( + factory = { context -> + val padding = + context.resources.getDimensionPixelSize( + R.dimen.keyguard_affordance_fixed_padding + ) + val view = + LaunchableImageView(context, null).apply { + id = viewId + scaleType = ImageView.ScaleType.FIT_CENTER + background = + ResourcesCompat.getDrawable( + context.resources, + R.drawable.keyguard_bottom_affordance_bg, + context.theme + ) + foreground = + ResourcesCompat.getDrawable( + context.resources, + R.drawable.keyguard_bottom_affordance_selected_border, + context.theme + ) + visibility = View.INVISIBLE + setPadding(padding, padding, padding, padding) + } + + setBinding( + KeyguardQuickAffordanceViewBinder.bind( + view, + viewModel, + transitionAlpha, + falsingManager, + vibratorHelper, + ) { + indicationController.showTransientIndication(it) + } + ) + + view + }, + onRelease = { binding?.destroy() }, + modifier = + modifier.size( + width = shortcutSizeDp().width, + height = shortcutSizeDp().height, + ) + ) + } + + @Composable + private fun IndicationArea( + indicationAreaViewModel: KeyguardIndicationAreaViewModel, + keyguardRootViewModel: KeyguardRootViewModel, + indicationController: KeyguardIndicationController, + modifier: Modifier = Modifier, + ) { + val (disposable, setDisposable) = mutableStateOf<DisposableHandle?>(null) + + AndroidView( + factory = { context -> + val view = KeyguardIndicationArea(context, null) + setDisposable( + KeyguardIndicationAreaBinder.bind( + view = view, + viewModel = indicationAreaViewModel, + keyguardRootViewModel = keyguardRootViewModel, + indicationController = indicationController, + ) + ) + view + }, + onRelease = { disposable?.dispose() }, + modifier = modifier.fillMaxWidth(), + ) + } +} + +private val StartButtonElementKey = ElementKey("StartButton") +private val EndButtonElementKey = ElementKey("EndButton") +private val IndicationAreaElementKey = ElementKey("IndicationArea") diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt new file mode 100644 index 000000000000..eaf8063b6f15 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.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.keyguard.ui.composable.section + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +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.ElementKey +import com.android.compose.animation.scene.SceneScope +import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel +import javax.inject.Inject + +class ClockSection +@Inject +constructor( + private val viewModel: KeyguardClockViewModel, +) { + @Composable + fun SceneScope.SmallClock(modifier: Modifier = Modifier) { + if (viewModel.useLargeClock) { + return + } + + MovableElement( + key = ClockElementKey, + modifier = modifier, + ) { + Box( + modifier = Modifier.fillMaxWidth().background(Color.Magenta), + ) { + Text( + text = "TODO(b/316211368): Small clock", + color = Color.White, + modifier = Modifier.align(Alignment.Center), + ) + } + } + } + + @Composable + fun SceneScope.LargeClock(modifier: Modifier = Modifier) { + if (!viewModel.useLargeClock) { + return + } + + MovableElement( + key = ClockElementKey, + modifier = modifier, + ) { + Box( + modifier = Modifier.fillMaxWidth().background(Color.Blue), + ) { + Text( + text = "TODO(b/316211368): Large clock", + color = Color.White, + modifier = Modifier.align(Alignment.Center), + ) + } + } + } +} + +private val ClockElementKey = ElementKey("Clock") diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt new file mode 100644 index 000000000000..8bbe424b9a49 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt @@ -0,0 +1,121 @@ +/* + * 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.keyguard.ui.composable.section + +import android.content.Context +import android.graphics.Point +import android.graphics.Rect +import android.util.DisplayMetrics +import android.view.WindowManager +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +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.ElementKey +import com.android.compose.animation.scene.SceneScope +import com.android.systemui.biometrics.AuthController +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.res.R +import javax.inject.Inject + +class LockSection +@Inject +constructor( + private val windowManager: WindowManager, + private val authController: AuthController, + private val featureFlags: FeatureFlagsClassic, +) { + @Composable + fun SceneScope.LockIcon(modifier: Modifier = Modifier) { + MovableElement( + key = LockIconElementKey, + modifier = modifier, + ) { + Box( + modifier = Modifier.background(Color.Red), + ) { + Text( + text = "TODO(b/316211368): Lock", + color = Color.White, + modifier = Modifier.align(Alignment.Center), + ) + } + } + } + + /** + * Returns the bounds of the lock icon, in window view coordinates. + * + * On devices that support UDFPS (under-display fingerprint sensor), the bounds of the icon are + * the same as the bounds of the sensor. + */ + fun lockIconBounds( + context: Context, + ): Rect { + val windowViewBounds = windowManager.currentWindowMetrics.bounds + var widthPx = windowViewBounds.right.toFloat() + if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) { + val insets = windowManager.currentWindowMetrics.windowInsets + // Assumed to be initially neglected as there are no left or right insets in portrait. + // However, on landscape, these insets need to included when calculating the midpoint. + @Suppress("DEPRECATION") + widthPx -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat() + } + val defaultDensity = + DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / + DisplayMetrics.DENSITY_DEFAULT.toFloat() + val lockIconRadiusPx = (defaultDensity * 36).toInt() + + val udfpsLocation = authController.udfpsLocation + return if (authController.isUdfpsSupported && udfpsLocation != null) { + centerLockIcon(udfpsLocation, authController.udfpsRadius) + } else { + val scaleFactor = authController.scaleFactor + val bottomPaddingPx = + context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) + val heightPx = windowViewBounds.bottom.toFloat() + + centerLockIcon( + Point( + (widthPx / 2).toInt(), + (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt() + ), + lockIconRadiusPx * scaleFactor + ) + } + } + + private fun centerLockIcon( + center: Point, + radius: Float, + ): Rect { + return Rect().apply { + set( + center.x - radius.toInt(), + center.y - radius.toInt(), + center.x + radius.toInt(), + center.y + radius.toInt(), + ) + } + } +} + +private val LockIconElementKey = ElementKey("LockIcon") diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt new file mode 100644 index 000000000000..f135be260cbd --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -0,0 +1,51 @@ +/* + * 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.keyguard.ui.composable.section + +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.ElementKey +import com.android.compose.animation.scene.SceneScope +import javax.inject.Inject + +class NotificationSection @Inject constructor() { + @Composable + fun SceneScope.Notifications(modifier: Modifier = Modifier) { + MovableElement( + key = NotificationsElementKey, + modifier = modifier, + ) { + Box( + modifier = Modifier.fillMaxSize().background(Color.Yellow), + ) { + Text( + text = "TODO(b/316211368): Notifications", + color = Color.White, + modifier = Modifier.align(Alignment.Center), + ) + } + } + } +} + +private val NotificationsElementKey = ElementKey("Notifications") diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt new file mode 100644 index 000000000000..ca03af566fb2 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt @@ -0,0 +1,48 @@ +/* + * 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.keyguard.ui.composable.section + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +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.ElementKey +import com.android.compose.animation.scene.SceneScope +import javax.inject.Inject + +class SmartSpaceSection @Inject constructor() { + @Composable + fun SceneScope.SmartSpace(modifier: Modifier = Modifier) { + MovableElement(key = SmartSpaceElementKey, modifier = modifier) { + Box( + modifier = Modifier.fillMaxWidth().background(Color.Cyan), + ) { + Text( + text = "TODO(b/316211368): Smart space", + color = Color.White, + modifier = Modifier.align(Alignment.Center), + ) + } + } + } +} + +private val SmartSpaceElementKey = ElementKey("SmartSpace") diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt new file mode 100644 index 000000000000..6811eb4cea5c --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt @@ -0,0 +1,89 @@ +/* + * 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.keyguard.ui.composable.section + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.SceneScope +import com.android.compose.modifiers.height +import com.android.keyguard.dagger.KeyguardStatusBarViewComponent +import com.android.systemui.res.R +import com.android.systemui.shade.NotificationPanelView +import com.android.systemui.shade.ShadeViewStateProvider +import com.android.systemui.statusbar.phone.KeyguardStatusBarView +import com.android.systemui.util.Utils +import dagger.Lazy +import javax.inject.Inject + +class StatusBarSection +@Inject +constructor( + private val componentFactory: KeyguardStatusBarViewComponent.Factory, + private val notificationPanelView: Lazy<NotificationPanelView>, +) { + @Composable + fun SceneScope.StatusBar(modifier: Modifier = Modifier) { + val context = LocalContext.current + + MovableElement( + key = StatusBarElementKey, + modifier = modifier, + ) { + AndroidView( + factory = { + notificationPanelView.get().findViewById<View>(R.id.keyguard_header)?.let { + (it.parent as ViewGroup).removeView(it) + } + + val provider = + object : ShadeViewStateProvider { + override val lockscreenShadeDragProgress: Float = 0f + override val panelViewExpandedHeight: Float = 0f + override fun shouldHeadsUpBeVisible(): Boolean { + return false + } + } + + @SuppressLint("InflateParams") + val view = + LayoutInflater.from(context) + .inflate( + R.layout.keyguard_status_bar, + null, + false, + ) as KeyguardStatusBarView + componentFactory.build(view, provider).keyguardStatusBarViewController.init() + view + }, + modifier = + Modifier.fillMaxWidth().height { + Utils.getStatusBarHeaderHeightKeyguard(context) + }, + ) + } + } +} + +private val StatusBarElementKey = ElementKey("StatusBar") diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index 9d9b0a9fe496..a85d9bff283e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -16,6 +16,7 @@ package com.android.compose.animation.scene +import android.graphics.Picture import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue @@ -66,12 +67,21 @@ internal class Element(val key: ElementKey) { * The movable content of this element, if this element is composed using * [SceneScope.MovableElement]. */ - val movableContent by - // This is only accessed from the composition (main) thread, so no need to use the default - // lock of lazy {} to synchronize. - lazy(mode = LazyThreadSafetyMode.NONE) { - movableContentOf { content: @Composable () -> Unit -> content() } - } + private var _movableContent: (@Composable (@Composable () -> Unit) -> Unit)? = null + val movableContent: @Composable (@Composable () -> Unit) -> Unit + get() = + _movableContent + ?: movableContentOf { content: @Composable () -> Unit -> content() } + .also { _movableContent = it } + + /** + * The [Picture] to which we save the last drawing commands of this element, if it is movable. + * This is necessary because the content of this element might not be composed in the scene it + * should currently be drawn. + */ + private var _picture: Picture? = null + val picture: Picture + get() = _picture ?: Picture().also { _picture = it } override fun toString(): String { return "Element(key=$key)" @@ -521,11 +531,6 @@ private fun IntermediateMeasureScope.place( sceneValues.targetOffset = targetOffsetInScene } - // No need to place the element in this scene if we don't want to draw it anyways. - if (!shouldDrawElement(layoutImpl, scene, element)) { - return - } - val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero) val lastSharedValues = element.lastSharedValues val lastValues = sceneValues.lastValues @@ -548,6 +553,13 @@ private fun IntermediateMeasureScope.place( lastSharedValues.offset = targetOffset lastValues.offset = targetOffset + // No need to place the element in this scene if we don't want to draw it anyways. Note that + // it's still important to compute the target offset and update lastValues, otherwise it + // will be out of date. + if (!shouldDrawElement(layoutImpl, scene, element)) { + return + } + val offset = (targetOffset - currentOffset).round() if (isElementOpaque(layoutImpl, element, scene, sceneValues)) { // TODO(b/291071158): Call placeWithLayer() if offset != IntOffset.Zero and size is not diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt index 306f27626e19..49df2f6b6062 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -16,7 +16,6 @@ package com.android.compose.animation.scene -import android.graphics.Picture import android.util.Log import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer @@ -60,7 +59,7 @@ internal fun MovableElement( // The [Picture] to which we save the last drawing commands of this element. This is // necessary because the content of this element might not be composed in this scene, in // which case we still need to draw it. - val picture = remember { Picture() } + val picture = element.picture // Whether we should compose the movable element here. The scene picker logic to know in // which scene we should compose/draw a movable element might depend on the current diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index 6a7a3a00d4fe..30e50a972230 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.layout.intermediateLayout import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.IntSize import androidx.compose.ui.zIndex +import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions /** A scene in a [SceneTransitionLayout]. */ @Stable @@ -152,4 +153,8 @@ private class SceneScopeImpl( bounds: ElementKey, shape: Shape ): Modifier = punchHole(layoutImpl, element, bounds, shape) + + override fun Modifier.noResizeDuringTransitions(): Modifier { + return noResizeDuringTransitions(layoutState = layoutImpl.state) + } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 3608e374fdbc..5eb339e4a5e4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -65,11 +65,11 @@ fun SceneTransitionLayout( SceneTransitionLayoutForTesting( currentScene, onChangeScene, + modifier, transitions, state, edgeDetector, transitionInterceptionThreshold, - modifier, onLayoutImpl = null, scenes, ) @@ -205,6 +205,12 @@ interface SceneScope { * the result. */ fun Modifier.punchHole(element: ElementKey, bounds: ElementKey, shape: Shape): Modifier + + /** + * Don't resize during transitions. This can for instance be used to make sure that scrollable + * lists keep a constant size during transitions even if its elements are growing/shrinking. + */ + fun Modifier.noResizeDuringTransitions(): Modifier } // TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey @@ -257,12 +263,12 @@ enum class SwipeDirection(val orientation: Orientation) { internal fun SceneTransitionLayoutForTesting( currentScene: SceneKey, onChangeScene: (SceneKey) -> Unit, - transitions: SceneTransitions, - state: SceneTransitionLayoutState, - edgeDetector: EdgeDetector, - transitionInterceptionThreshold: Float, - modifier: Modifier, - onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)?, + modifier: Modifier = Modifier, + transitions: SceneTransitions = transitions {}, + state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) }, + edgeDetector: EdgeDetector = DefaultEdgeDetector, + transitionInterceptionThreshold: Float = 0f, + onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null, scenes: SceneTransitionLayoutScope.() -> Unit, ) { val density = LocalDensity.current @@ -280,6 +286,10 @@ internal fun SceneTransitionLayoutForTesting( .also { onLayoutImpl?.invoke(it) } } + // TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a + // SnapshotStateMap anymore. + layoutImpl.updateScenes(scenes) + val targetSceneChannel = remember { Channel<SceneKey>(Channel.CONFLATED) } SideEffect { if (state != layoutImpl.state) { @@ -293,7 +303,6 @@ internal fun SceneTransitionLayoutForTesting( (state as SceneTransitionLayoutStateImpl).transitions = transitions layoutImpl.density = density layoutImpl.edgeDetector = edgeDetector - layoutImpl.updateScenes(scenes) state.transitions = transitions diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index c99c3250bbb1..45e1a0fa8f77 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -46,7 +46,12 @@ internal class SceneTransitionLayoutImpl( builder: SceneTransitionLayoutScope.() -> Unit, coroutineScope: CoroutineScope, ) { - internal val scenes = mutableMapOf<SceneKey, Scene>() + /** + * The map of [Scene]s. + * + * TODO(b/317014852): Make this a normal MutableMap instead. + */ + internal val scenes = SnapshotStateMap<SceneKey, Scene>() /** * The map of [Element]s. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt new file mode 100644 index 000000000000..bd36cb8655ac --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene.modifiers + +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.intermediateLayout +import androidx.compose.ui.unit.Constraints +import com.android.compose.animation.scene.SceneTransitionLayoutState + +@OptIn(ExperimentalComposeUiApi::class) +internal fun Modifier.noResizeDuringTransitions(layoutState: SceneTransitionLayoutState): Modifier { + return intermediateLayout { measurable, constraints -> + if (layoutState.currentTransition == null) { + return@intermediateLayout measurable.measure(constraints).run { + layout(width, height) { place(0, 0) } + } + } + + // Make sure that this layout node has the same size than when we are at rest. + val sizeAtRest = lookaheadSize + measurable.measure(Constraints.fixed(sizeAtRest.width, sizeAtRest.height)).run { + layout(width, height) { place(0, 0) } + } + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index d332910c54b1..da5a0a04ed63 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -16,6 +16,7 @@ package com.android.compose.animation.scene +import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box @@ -30,16 +31,21 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.layout.intermediateLayout +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.test.subjects.DpOffsetSubject +import com.android.compose.test.subjects.assertThat import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -259,11 +265,6 @@ class ElementTest { SceneTransitionLayoutForTesting( currentScene = currentScene, onChangeScene = { currentScene = it }, - transitions = remember { transitions {} }, - state = remember { SceneTransitionLayoutState(currentScene) }, - edgeDetector = DefaultEdgeDetector, - modifier = Modifier, - transitionInterceptionThreshold = 0f, onLayoutImpl = { nullableLayoutImpl = it }, ) { scene(TestScenes.SceneA) { /* Nothing */} @@ -429,11 +430,6 @@ class ElementTest { SceneTransitionLayoutForTesting( currentScene = TestScenes.SceneA, onChangeScene = {}, - transitions = remember { transitions {} }, - state = remember { SceneTransitionLayoutState(TestScenes.SceneA) }, - edgeDetector = DefaultEdgeDetector, - modifier = Modifier, - transitionInterceptionThreshold = 0f, onLayoutImpl = { nullableLayoutImpl = it }, ) { scene(TestScenes.SceneA) { Box(Modifier.element(key)) } @@ -484,11 +480,6 @@ class ElementTest { SceneTransitionLayoutForTesting( currentScene = TestScenes.SceneA, onChangeScene = {}, - transitions = remember { transitions {} }, - state = remember { SceneTransitionLayoutState(TestScenes.SceneA) }, - edgeDetector = DefaultEdgeDetector, - modifier = Modifier, - transitionInterceptionThreshold = 0f, onLayoutImpl = { nullableLayoutImpl = it }, ) { scene(TestScenes.SceneA) { @@ -574,4 +565,86 @@ class ElementTest { after { assertThat(fooCompositions).isEqualTo(1) } } } + + @Test + fun sharedElementOffsetIsUpdatedEvenWhenNotPlaced() { + var nullableLayoutImpl: SceneTransitionLayoutImpl? = null + var density: Density? = null + + fun layoutImpl() = nullableLayoutImpl ?: error("nullableLayoutImpl was not set") + + fun density() = density ?: error("density was not set") + + fun Offset.toDpOffset() = with(density()) { DpOffset(x.toDp(), y.toDp()) } + + fun foo() = layoutImpl().elements[TestElements.Foo] ?: error("Foo not in elements map") + + fun Element.lastSharedOffset() = lastSharedValues.offset.toDpOffset() + + fun Element.lastOffsetIn(scene: SceneKey) = + (sceneValues[scene] ?: error("$scene not in sceneValues map")) + .lastValues + .offset + .toDpOffset() + + rule.testTransition( + from = TestScenes.SceneA, + to = TestScenes.SceneB, + transitionLayout = { currentScene, onChangeScene -> + density = LocalDensity.current + + SceneTransitionLayoutForTesting( + currentScene = currentScene, + onChangeScene = onChangeScene, + onLayoutImpl = { nullableLayoutImpl = it }, + transitions = + transitions { + from(TestScenes.SceneA, to = TestScenes.SceneB) { + spec = tween(durationMillis = 4 * 16, easing = LinearEasing) + } + } + ) { + scene(TestScenes.SceneA) { Box(Modifier.element(TestElements.Foo)) } + scene(TestScenes.SceneB) { + Box(Modifier.offset(x = 40.dp, y = 80.dp).element(TestElements.Foo)) + } + } + } + ) { + val tolerance = DpOffsetSubject.DefaultTolerance + + before { + val expected = DpOffset(0.dp, 0.dp) + assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected) + assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected) + } + + at(16) { + val expected = DpOffset(10.dp, 20.dp) + assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected) + assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected) + assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected) + } + + at(32) { + val expected = DpOffset(20.dp, 40.dp) + assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected) + assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected) + assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected) + } + + at(48) { + val expected = DpOffset(30.dp, 60.dp) + assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected) + assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected) + assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected) + } + + after { + val expected = DpOffset(40.dp, 80.dp) + assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected) + assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected) + } + } + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt new file mode 100644 index 000000000000..2c159d15fa66 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene.modifiers + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestElements +import com.android.compose.animation.scene.element +import com.android.compose.animation.scene.testTransition +import com.android.compose.test.assertSizeIsEqualTo +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SizeTest { + @get:Rule val rule = createComposeRule() + + @Test + fun noResizeDuringTransitions() { + // The tag for the parent of the shared Foo element. + val parentTag = "parent" + + rule.testTransition( + fromSceneContent = { Box(Modifier.element(TestElements.Foo).size(100.dp)) }, + toSceneContent = { + // Don't resize the parent of Foo during transitions so that it's always the same + // size as when there is no transition (200dp). + Box(Modifier.noResizeDuringTransitions().testTag(parentTag)) { + Box(Modifier.element(TestElements.Foo).size(200.dp)) + } + }, + transition = { spec = tween(durationMillis = 4 * 16, easing = LinearEasing) }, + ) { + at(16) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) } + at(32) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) } + at(48) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) } + after { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) } + } + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt index 0b7c3f987db2..26da1f025544 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt @@ -17,56 +17,39 @@ package com.android.systemui.shared.notifications.data.repository import android.provider.Settings -import com.android.systemui.shared.notifications.shared.model.NotificationSettingsModel import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext -/** Provides access to state related to notifications. */ +/** Provides access to state related to notification settings. */ class NotificationSettingsRepository( scope: CoroutineScope, private val backgroundDispatcher: CoroutineDispatcher, private val secureSettingsRepository: SecureSettingsRepository, ) { /** The current state of the notification setting. */ - val settings: SharedFlow<NotificationSettingsModel> = + val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> = secureSettingsRepository .intSetting( name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, ) - .map { lockScreenShowNotificationsInt -> - NotificationSettingsModel( - isShowNotificationsOnLockScreenEnabled = lockScreenShowNotificationsInt == 1, - ) - } - .shareIn( + .map { it == 1 } + .stateIn( scope = scope, started = SharingStarted.WhileSubscribed(), - replay = 1, + initialValue = false, ) - suspend fun getSettings(): NotificationSettingsModel { - return withContext(backgroundDispatcher) { - NotificationSettingsModel( - isShowNotificationsOnLockScreenEnabled = - secureSettingsRepository.get( - name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, - defaultValue = 0, - ) == 1 - ) - } - } - - suspend fun setSettings(model: NotificationSettingsModel) { + suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) { withContext(backgroundDispatcher) { secureSettingsRepository.set( name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, - value = if (model.isShowNotificationsOnLockScreenEnabled) 1 else 0, + value = if (enabled) 1 else 0, ) } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt index 21f3acaf15ff..9ec6ec8d3811 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt @@ -17,32 +17,23 @@ package com.android.systemui.shared.notifications.domain.interactor import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository -import com.android.systemui.shared.notifications.shared.model.NotificationSettingsModel -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow /** Encapsulates business logic for interacting with notification settings. */ class NotificationSettingsInteractor( private val repository: NotificationSettingsRepository, ) { - /** The current state of the notification setting. */ - val settings: Flow<NotificationSettingsModel> = repository.settings + /** Should notifications be visible on the lockscreen? */ + val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> = + repository.isShowNotificationsOnLockScreenEnabled - /** Toggles the setting to show or hide notifications on the lock screen. */ - suspend fun toggleShowNotificationsOnLockScreenEnabled() { - val currentModel = repository.getSettings() - setSettings( - currentModel.copy( - isShowNotificationsOnLockScreenEnabled = - !currentModel.isShowNotificationsOnLockScreenEnabled, - ) - ) - } - - suspend fun setSettings(model: NotificationSettingsModel) { - repository.setSettings(model) + suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) { + repository.setShowNotificationsOnLockscreenEnabled(enabled) } - suspend fun getSettings(): NotificationSettingsModel { - return repository.getSettings() + /** Toggles the setting to show or hide notifications on the lock screen. */ + suspend fun toggleShowNotificationsOnLockscreenEnabled() { + val current = repository.isShowNotificationsOnLockScreenEnabled.value + repository.setShowNotificationsOnLockscreenEnabled(!current) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index 64ddbc7828ac..c961be946f79 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt @@ -35,8 +35,10 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobile import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.function.Function +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runCurrent @@ -58,6 +60,7 @@ class AuthenticationRepositoryTest : SysuiTestCase() { private val testUtils = SceneTestUtils(this) private val testScope = testUtils.testScope + private val clock = FakeSystemClock() private val userRepository = FakeUserRepository() private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository @@ -78,8 +81,10 @@ class AuthenticationRepositoryTest : SysuiTestCase() { underTest = AuthenticationRepositoryImpl( applicationScope = testScope.backgroundScope, - getSecurityMode = getSecurityMode, backgroundDispatcher = testUtils.testDispatcher, + flags = testUtils.sceneContainerFlags, + clock = clock, + getSecurityMode = getSecurityMode, userRepository = userRepository, lockPatternUtils = lockPatternUtils, broadcastDispatcher = fakeBroadcastDispatcher, @@ -141,22 +146,6 @@ class AuthenticationRepositoryTest : SysuiTestCase() { } @Test - fun reportAuthenticationAttempt_emitsAuthenticationChallengeResult() = - testScope.runTest { - val authenticationChallengeResults by - collectValues(underTest.authenticationChallengeResult) - - runCurrent() - underTest.reportAuthenticationAttempt(true) - runCurrent() - underTest.reportAuthenticationAttempt(false) - runCurrent() - underTest.reportAuthenticationAttempt(true) - - assertThat(authenticationChallengeResults).isEqualTo(listOf(true, false, true)) - } - - @Test fun isPinEnhancedPrivacyEnabled() = testScope.runTest { whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[0].id)) @@ -172,6 +161,45 @@ class AuthenticationRepositoryTest : SysuiTestCase() { assertThat(values.last()).isTrue() } + @Test + fun lockoutEndTimestamp() = + testScope.runTest { + val lockoutEndMs = clock.elapsedRealtime() + 30.seconds.inWholeMilliseconds + whenever(lockPatternUtils.getLockoutAttemptDeadline(USER_INFOS[0].id)) + .thenReturn(lockoutEndMs) + whenever(lockPatternUtils.getLockoutAttemptDeadline(USER_INFOS[1].id)).thenReturn(0) + + // Switch to a user who is not locked-out. + userRepository.setSelectedUserInfo(USER_INFOS[1]) + assertThat(underTest.lockoutEndTimestamp).isNull() + + // Switch back to the locked-out user, verify the timestamp is up-to-date. + userRepository.setSelectedUserInfo(USER_INFOS[0]) + assertThat(underTest.lockoutEndTimestamp).isEqualTo(lockoutEndMs) + + // After the lockout expires, null is returned. + clock.setElapsedRealtime(lockoutEndMs) + assertThat(underTest.lockoutEndTimestamp).isNull() + } + + @Test + fun hasLockoutOccurred() = + testScope.runTest { + val hasLockoutOccurred by collectLastValue(underTest.hasLockoutOccurred) + assertThat(hasLockoutOccurred).isFalse() + + underTest.reportLockoutStarted(1000) + assertThat(hasLockoutOccurred).isTrue() + + clock.setElapsedRealtime(clock.elapsedRealtime() + 60.seconds.inWholeMilliseconds) + + underTest.reportAuthenticationAttempt(isSuccessful = false) + assertThat(hasLockoutOccurred).isTrue() + + underTest.reportAuthenticationAttempt(isSuccessful = true) + assertThat(hasLockoutOccurred).isFalse() + } + private fun setSecurityModeAndDispatchBroadcast( securityMode: KeyguardSecurityModel.SecurityMode, ) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 08cd7edba6af..c113b3744bc0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -21,14 +21,18 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.currentTime import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -43,21 +47,23 @@ class AuthenticationInteractorTest : SysuiTestCase() { private val testScope = utils.testScope private val underTest = utils.authenticationInteractor() + private val onAuthenticationResult by + testScope.collectLastValue(underTest.onAuthenticationResult) + private val failedAuthenticationAttempts by + testScope.collectLastValue(underTest.failedAuthenticationAttempts) + @Test fun authenticationMethod() = testScope.runTest { val authMethod by collectLastValue(underTest.authenticationMethod) runCurrent() - assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin) - assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin) + assertThat(authMethod).isEqualTo(Pin) + assertThat(underTest.getAuthenticationMethod()).isEqualTo(Pin) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) + utils.authenticationRepository.setAuthenticationMethod(Password) - assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password) - assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(AuthenticationMethodModel.Password) + assertThat(authMethod).isEqualTo(Password) + assertThat(underTest.getAuthenticationMethod()).isEqualTo(Password) } @Test @@ -66,51 +72,45 @@ class AuthenticationInteractorTest : SysuiTestCase() { val authMethod by collectLastValue(underTest.authenticationMethod) runCurrent() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setAuthenticationMethod(None) - assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None) - assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(AuthenticationMethodModel.None) + assertThat(authMethod).isEqualTo(None) + assertThat(underTest.getAuthenticationMethod()).isEqualTo(None) } @Test fun authenticate_withCorrectPin_succeeds() = testScope.runTest { - val lockout by collectLastValue(underTest.lockout) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod(Pin) - assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) - .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(lockout).isNull() - assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) + assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) } @Test fun authenticate_withIncorrectPin_fails() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod(Pin) - assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))) - .isEqualTo(AuthenticationResult.FAILED) + assertFailed(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))) } @Test(expected = IllegalArgumentException::class) fun authenticate_withEmptyPin_throwsException() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod(Pin) underTest.authenticate(listOf()) } @Test fun authenticate_withCorrectMaxLengthPin_succeeds() = testScope.runTest { - val pin = List(16) { 9 } + val correctMaxLengthPin = List(16) { 9 } utils.authenticationRepository.apply { - setAuthenticationMethod(AuthenticationMethodModel.Pin) - overrideCredential(pin) + setAuthenticationMethod(Pin) + overrideCredential(correctMaxLengthPin) } - assertThat(underTest.authenticate(pin)).isEqualTo(AuthenticationResult.SUCCEEDED) + assertSucceeded(underTest.authenticate(correctMaxLengthPin)) } @Test @@ -122,88 +122,64 @@ class AuthenticationInteractorTest : SysuiTestCase() { // If the policy changes, there is work to do in SysUI. assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - assertThat(underTest.authenticate(List(17) { 9 })) - .isEqualTo(AuthenticationResult.FAILED) + utils.authenticationRepository.setAuthenticationMethod(Pin) + + assertFailed(underTest.authenticate(List(17) { 9 })) } @Test fun authenticate_withCorrectPassword_succeeds() = testScope.runTest { - val lockout by collectLastValue(underTest.lockout) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) + utils.authenticationRepository.setAuthenticationMethod(Password) - assertThat(underTest.authenticate("password".toList())) - .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(lockout).isNull() - assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) + assertSucceeded(underTest.authenticate("password".toList())) } @Test fun authenticate_withIncorrectPassword_fails() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) + utils.authenticationRepository.setAuthenticationMethod(Password) - assertThat(underTest.authenticate("alohomora".toList())) - .isEqualTo(AuthenticationResult.FAILED) + assertFailed(underTest.authenticate("alohomora".toList())) } @Test fun authenticate_withCorrectPattern_succeeds() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern - ) + utils.authenticationRepository.setAuthenticationMethod(Pattern) - assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)) - .isEqualTo(AuthenticationResult.SUCCEEDED) + assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.PATTERN)) } @Test fun authenticate_withIncorrectPattern_fails() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern - ) - - assertThat( - underTest.authenticate( - listOf( - AuthenticationPatternCoordinate(x = 2, y = 0), - AuthenticationPatternCoordinate(x = 2, y = 1), - AuthenticationPatternCoordinate(x = 2, y = 2), - AuthenticationPatternCoordinate(x = 1, y = 2), - ) - ) + utils.authenticationRepository.setAuthenticationMethod(Pattern) + val wrongPattern = + listOf( + AuthenticationPatternCoordinate(x = 2, y = 0), + AuthenticationPatternCoordinate(x = 2, y = 1), + AuthenticationPatternCoordinate(x = 2, y = 2), + AuthenticationPatternCoordinate(x = 1, y = 2), ) - .isEqualTo(AuthenticationResult.FAILED) + + assertFailed(underTest.authenticate(wrongPattern)) } @Test fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - val lockout by collectLastValue(underTest.lockout) utils.authenticationRepository.apply { - setAuthenticationMethod(AuthenticationMethodModel.Pin) + setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) } assertThat(isAutoConfirmEnabled).isTrue() + val shorterPin = + FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply { removeLast() } - assertThat( - underTest.authenticate( - FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply { - removeLast() - }, - tryAutoConfirm = true - ) - ) - .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(lockout).isNull() + assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true)) + assertThat(underTest.lockoutEndTimestamp).isNull() assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) } @@ -212,18 +188,17 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) utils.authenticationRepository.apply { - setAuthenticationMethod(AuthenticationMethodModel.Pin) + setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) } assertThat(isAutoConfirmEnabled).isTrue() - assertThat( - underTest.authenticate( - FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }, - tryAutoConfirm = true - ) - ) - .isEqualTo(AuthenticationResult.FAILED) + val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 } + + assertFailed( + underTest.authenticate(wrongPin, tryAutoConfirm = true), + assertNoResultEvents = true, + ) } @Test @@ -231,18 +206,17 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) utils.authenticationRepository.apply { - setAuthenticationMethod(AuthenticationMethodModel.Pin) + setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) } assertThat(isAutoConfirmEnabled).isTrue() - assertThat( - underTest.authenticate( - FakeAuthenticationRepository.DEFAULT_PIN + listOf(7), - tryAutoConfirm = true - ) - ) - .isEqualTo(AuthenticationResult.FAILED) + val longerPin = FakeAuthenticationRepository.DEFAULT_PIN + listOf(7) + + assertFailed( + underTest.authenticate(longerPin, tryAutoConfirm = true), + assertNoResultEvents = true, + ) } @Test @@ -250,69 +224,54 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) utils.authenticationRepository.apply { - setAuthenticationMethod(AuthenticationMethodModel.Pin) + setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) } assertThat(isAutoConfirmEnabled).isTrue() - assertThat( - underTest.authenticate( - FakeAuthenticationRepository.DEFAULT_PIN, - tryAutoConfirm = true - ) - ) - .isEqualTo(AuthenticationResult.SUCCEEDED) + val correctPin = FakeAuthenticationRepository.DEFAULT_PIN + + assertSucceeded(underTest.authenticate(correctPin, tryAutoConfirm = true)) } @Test fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringLockout_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(AuthenticationMethodModel.Pin) + setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) - setLockoutDuration(42) + reportLockoutStarted(42) } - val authResult = - underTest.authenticate( - FakeAuthenticationRepository.DEFAULT_PIN, - tryAutoConfirm = true - ) + val correctPin = FakeAuthenticationRepository.DEFAULT_PIN - assertThat(authResult).isEqualTo(AuthenticationResult.SKIPPED) + assertSkipped(underTest.authenticate(correctPin, tryAutoConfirm = true)) assertThat(isAutoConfirmEnabled).isFalse() - assertThat(isUnlocked).isFalse() assertThat(hintedPinLength).isNull() + assertThat(underTest.lockoutEndTimestamp).isNotNull() } @Test fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() = testScope.runTest { utils.authenticationRepository.apply { - setAuthenticationMethod(AuthenticationMethodModel.Pin) + setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(false) } - assertThat( - underTest.authenticate( - FakeAuthenticationRepository.DEFAULT_PIN, - tryAutoConfirm = true - ) - ) - .isEqualTo(AuthenticationResult.SKIPPED) + + val correctPin = FakeAuthenticationRepository.DEFAULT_PIN + + assertSkipped(underTest.authenticate(correctPin, tryAutoConfirm = true)) } @Test fun tryAutoConfirm_withoutCorrectPassword_returnsNull() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) + utils.authenticationRepository.setAuthenticationMethod(Password) - assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)) - .isEqualTo(AuthenticationResult.SKIPPED) + assertSkipped(underTest.authenticate("password".toList(), tryAutoConfirm = true)) } @Test @@ -337,7 +296,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - val lockout by collectLastValue(underTest.lockout) utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) // The feature is enabled. @@ -345,92 +303,104 @@ class AuthenticationInteractorTest : SysuiTestCase() { // Make many wrong attempts to trigger lockout. repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { - underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN + assertFailed(underTest.authenticate(listOf(5, 6, 7))) // Wrong PIN } - assertThat(lockout).isNotNull() + assertThat(underTest.lockoutEndTimestamp).isNotNull() assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) // Lockout disabled auto-confirm. assertThat(isAutoConfirmEnabled).isFalse() // Move the clock forward one more second, to completely finish the lockout period: - advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_MS + 1000L) - assertThat(lockout).isNull() + advanceTimeBy( + FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds.plus(1.seconds) + ) + assertThat(underTest.lockoutEndTimestamp).isNull() // Auto-confirm is still disabled, because lockout occurred at least once in this // session. assertThat(isAutoConfirmEnabled).isFalse() // Correct PIN and unlocks successfully, resetting the 'session'. - assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) - .isEqualTo(AuthenticationResult.SUCCEEDED) + assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) // Auto-confirm is re-enabled. assertThat(isAutoConfirmEnabled).isTrue() + } - assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) + @Test + fun failedAuthenticationAttempts() = + testScope.runTest { + val failedAuthenticationAttempts by + collectLastValue(underTest.failedAuthenticationAttempts) + + utils.authenticationRepository.setAuthenticationMethod(Pin) + val correctPin = FakeAuthenticationRepository.DEFAULT_PIN + + assertSucceeded(underTest.authenticate(correctPin)) + assertThat(failedAuthenticationAttempts).isEqualTo(0) + + // Make many wrong attempts, leading to lockout: + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { index -> + underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN + assertThat(failedAuthenticationAttempts).isEqualTo(index + 1) + } + + // Correct PIN, but locked out, so doesn't attempt it: + assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false) + assertThat(failedAuthenticationAttempts) + .isEqualTo(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) + + // Move the clock forward to finish the lockout period: + advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds) + assertThat(failedAuthenticationAttempts) + .isEqualTo(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) + + // Correct PIN and no longer locked out so unlocks successfully: + assertSucceeded(underTest.authenticate(correctPin)) + assertThat(failedAuthenticationAttempts).isEqualTo(0) } @Test - fun lockout() = + fun lockoutEndTimestamp() = testScope.runTest { - val lockout by collectLastValue(underTest.lockout) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) - assertThat(lockout).isNull() + utils.authenticationRepository.setAuthenticationMethod(Pin) + val correctPin = FakeAuthenticationRepository.DEFAULT_PIN + + underTest.authenticate(correctPin) + assertThat(underTest.lockoutEndTimestamp).isNull() // Make many wrong attempts, but just shy of what's needed to get locked out: repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) { underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN - assertThat(lockout).isNull() + assertThat(underTest.lockoutEndTimestamp).isNull() } // Make one more wrong attempt, leading to lockout: underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN - assertThat(lockout) - .isEqualTo( - AuthenticationLockoutModel( - failedAttemptCount = - FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT, - remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS, - ) - ) + + val expectedLockoutEndTimestamp = + testScope.currentTime + FakeAuthenticationRepository.LOCKOUT_DURATION_MS + assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp) assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) // Correct PIN, but locked out, so doesn't attempt it: - assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) - .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(lockout) - .isEqualTo( - AuthenticationLockoutModel( - failedAttemptCount = - FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT, - remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS, - ) - ) + assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false) + assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp) // Move the clock forward to ALMOST skip the lockout, leaving one second to go: - val lockoutTimeoutSec = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) { time -> - advanceTimeBy(1000) - assertThat(lockout) - .isEqualTo( - AuthenticationLockoutModel( - failedAttemptCount = - FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT, - remainingSeconds = lockoutTimeoutSec - (time + 1), - ) - ) + repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) { + advanceTimeBy(1.seconds) + assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp) } // Move the clock forward one more second, to completely finish the lockout period: - advanceTimeBy(1000) - assertThat(lockout).isNull() + advanceTimeBy(1.seconds) + assertThat(underTest.lockoutEndTimestamp).isNull() // Correct PIN and no longer locked out so unlocks successfully: - assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) - .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(lockout).isNull() + assertSucceeded(underTest.authenticate(correctPin)) + assertThat(underTest.lockoutEndTimestamp).isNull() } @Test @@ -438,7 +408,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(AuthenticationMethodModel.Pin) + setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(false) } @@ -450,7 +420,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(AuthenticationMethodModel.Pin) + setAuthenticationMethod(Pin) overrideCredential( buildList { repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) } @@ -467,7 +437,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(AuthenticationMethodModel.Pin) + setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) overrideCredential( buildList { @@ -484,7 +454,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(AuthenticationMethodModel.Pin) + setAuthenticationMethod(Pin) overrideCredential( buildList { repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) } @@ -499,18 +469,45 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withTooShortPassword() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) - assertThat( - underTest.authenticate( - buildList { - repeat(utils.authenticationRepository.minPasswordLength - 1) { time -> - add("$time") - } - } - ) - ) - .isEqualTo(AuthenticationResult.SKIPPED) + utils.authenticationRepository.setAuthenticationMethod(Password) + + val tooShortPassword = buildList { + repeat(utils.authenticationRepository.minPasswordLength - 1) { time -> + add("$time") + } + } + assertSkipped(underTest.authenticate(tooShortPassword)) + } + + private fun assertSucceeded(authenticationResult: AuthenticationResult) { + assertThat(authenticationResult).isEqualTo(AuthenticationResult.SUCCEEDED) + assertThat(onAuthenticationResult).isTrue() + assertThat(underTest.lockoutEndTimestamp).isNull() + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) + assertThat(failedAuthenticationAttempts).isEqualTo(0) + } + + private fun assertFailed( + authenticationResult: AuthenticationResult, + assertNoResultEvents: Boolean = false, + ) { + assertThat(authenticationResult).isEqualTo(AuthenticationResult.FAILED) + if (assertNoResultEvents) { + assertThat(onAuthenticationResult).isNull() + } else { + assertThat(onAuthenticationResult).isFalse() + } + } + + private fun assertSkipped( + authenticationResult: AuthenticationResult, + assertNoResultEvents: Boolean = true, + ) { + assertThat(authenticationResult).isEqualTo(AuthenticationResult.SKIPPED) + if (assertNoResultEvents) { + assertThat(onAuthenticationResult).isNull() + } else { + assertThat(onAuthenticationResult).isNotNull() } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index dddcf18c1ede..a4b55e765c92 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -90,7 +90,6 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; -import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -124,8 +123,6 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; -import javax.inject.Provider; - @SmallTest @RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) @@ -216,8 +213,6 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; @Mock - private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; - @Mock private SelectedUserInteractor mSelectedUserInteractor; // Capture listeners so that they can be used to send events @@ -339,7 +334,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mInputManager, mock(KeyguardFaceAuthInteractor.class), mUdfpsKeyguardAccessibilityDelegate, - mUdfpsKeyguardViewModels, mSelectedUserInteractor, mFpsUnlockTracker, mKeyguardTransitionInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt index 0ab596c82d6f..1f8854ffc1c8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -616,4 +616,28 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : .onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)) job.cancel() } + + @Test + fun bouncerToAod_dozeAmountChanged() = + testScope.runTest { + // GIVEN view is attached + mController.onViewAttached() + Mockito.reset(mView) + + val job = mController.listenForPrimaryBouncerToAodTransitions(this) + // WHEN alternate bouncer to aod transition in progress + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.AOD, + value = .3f, + transitionState = TransitionState.RUNNING + ) + ) + runCurrent() + + // THEN doze amount is updated to + verify(mView).onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATE_APPEAR_ON_SCREEN_OFF)) + job.cancel() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 9b1df7c0ffc0..99c18744f9b4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -21,15 +21,15 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationResult -import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.res.R import com.android.systemui.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat -import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent @@ -79,7 +79,7 @@ class BouncerInteractorTest : SysuiTestCase() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() underTest.clearMessage() - assertThat(message).isEmpty() + assertThat(message).isNull() underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) @@ -149,7 +149,7 @@ class BouncerInteractorTest : SysuiTestCase() { // Incomplete input. assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(message).isEmpty() + assertThat(message).isNull() // Correct input. assertThat( @@ -159,7 +159,7 @@ class BouncerInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(message).isEmpty() + assertThat(message).isNull() } @Test @@ -246,57 +246,40 @@ class BouncerInteractorTest : SysuiTestCase() { } @Test - fun lockout() = + fun lockoutStarted() = testScope.runTest { - val lockout by collectLastValue(underTest.lockout) + val lockoutStartedEvents by collectValues(underTest.onLockoutStarted) val message by collectLastValue(underTest.message) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - assertThat(lockout).isNull() + assertThat(lockoutStartedEvents).isEmpty() + + // Try the wrong PIN repeatedly, until lockout is triggered: repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times -> // Wrong PIN. assertThat(underTest.authenticate(listOf(6, 7, 8, 9))) .isEqualTo(AuthenticationResult.FAILED) if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) { - assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) + assertThat(lockoutStartedEvents).isEmpty() + assertThat(message).isNotEmpty() } } - assertThat(lockout) - .isEqualTo( - AuthenticationLockoutModel( - failedAttemptCount = - FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT, - remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS, - ) - ) - assertTryAgainMessage( - message, - FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt() - ) - - // Correct PIN, but locked out, so doesn't change away from the bouncer scene: - assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) - .isEqualTo(AuthenticationResult.SKIPPED) - assertTryAgainMessage( - message, - FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt() - ) - - lockout?.remainingSeconds?.let { seconds -> - repeat(seconds) { time -> - advanceTimeBy(1000) - val remainingTimeSec = seconds - time - 1 - if (remainingTimeSec > 0) { - assertTryAgainMessage(message, remainingTimeSec) - } - } + assertThat(authenticationInteractor.lockoutEndTimestamp).isNotNull() + assertThat(lockoutStartedEvents.size).isEqualTo(1) + assertThat(message).isNull() + + // Advance the time to finish the lockout: + advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds) + assertThat(authenticationInteractor.lockoutEndTimestamp).isNull() + assertThat(message).isNull() + assertThat(lockoutStartedEvents.size).isEqualTo(1) + + // Trigger lockout again: + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { + // Wrong PIN. + underTest.authenticate(listOf(6, 7, 8, 9)) } - assertThat(message).isEqualTo("") - assertThat(lockout).isNull() - - // Correct PIN and no longer locked out so changes to the Gone scene: - assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) - .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(lockout).isNull() + assertThat(lockoutStartedEvents.size).isEqualTo(2) } @Test @@ -326,13 +309,6 @@ class BouncerInteractorTest : SysuiTestCase() { verify(keyguardFaceAuthInteractor).onPrimaryBouncerUserInput() } - private fun assertTryAgainMessage( - message: String?, - time: Int, - ) { - assertThat(message).isEqualTo("Try again in $time seconds.") - } - companion object { private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN" private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 16a935943dbf..4be9b0a49a69 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.currentTime import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -135,19 +136,47 @@ class BouncerViewModelTest : SysuiTestCase() { fun message() = testScope.runTest { val message by collectLastValue(underTest.message) - val lockout by collectLastValue(bouncerInteractor.lockout) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(message?.isUpdateAnimated).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { - // Wrong PIN. - bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) + bouncerInteractor.authenticate(WRONG_PIN) } assertThat(message?.isUpdateAnimated).isFalse() - lockout?.remainingSeconds?.let { remainingSeconds -> - advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds) + val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0 + advanceTimeBy(lockoutEndMs - testScope.currentTime) + assertThat(message?.isUpdateAnimated).isTrue() + } + + @Test + fun lockoutMessage() = + testScope.runTest { + val authMethodViewModel by collectLastValue(underTest.authMethodViewModel) + val message by collectLastValue(underTest.message) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + assertThat(utils.authenticationRepository.lockoutEndTimestamp).isNull() + assertThat(authMethodViewModel?.lockoutMessageId).isNotNull() + + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times -> + bouncerInteractor.authenticate(WRONG_PIN) + if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) { + assertThat(message?.text).isEqualTo(bouncerInteractor.message.value) + assertThat(message?.isUpdateAnimated).isTrue() + } } + val lockoutSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS + assertTryAgainMessage(message?.text, lockoutSeconds) + assertThat(message?.isUpdateAnimated).isFalse() + + repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS) { time -> + advanceTimeBy(1.seconds) + val remainingSeconds = lockoutSeconds - time - 1 + if (remainingSeconds > 0) { + assertTryAgainMessage(message?.text, remainingSeconds) + } + } + assertThat(message?.text).isEmpty() assertThat(message?.isUpdateAnimated).isTrue() } @@ -160,32 +189,30 @@ class BouncerViewModelTest : SysuiTestCase() { authViewModel?.isInputEnabled ?: emptyFlow() } ) - val lockout by collectLastValue(bouncerInteractor.lockout) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(isInputEnabled).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { - // Wrong PIN. - bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) + bouncerInteractor.authenticate(WRONG_PIN) } assertThat(isInputEnabled).isFalse() - lockout?.remainingSeconds?.let { remainingSeconds -> - advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds) - } + val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0 + advanceTimeBy(lockoutEndMs - testScope.currentTime) assertThat(isInputEnabled).isTrue() } @Test fun dialogMessage() = testScope.runTest { + val authMethodViewModel by collectLastValue(underTest.authMethodViewModel) val dialogMessage by collectLastValue(underTest.dialogMessage) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + assertThat(authMethodViewModel?.lockoutMessageId).isNotNull() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { - // Wrong PIN. assertThat(dialogMessage).isNull() - bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) + bouncerInteractor.authenticate(WRONG_PIN) } assertThat(dialogMessage).isNotEmpty() @@ -241,4 +268,15 @@ class BouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Sim, ) } + + private fun assertTryAgainMessage( + message: String?, + time: Int, + ) { + assertThat(message).isEqualTo("Try again in $time seconds.") + } + + companion object { + private val WRONG_PIN = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 6d6baa57bb9d..64e6e5707d75 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues @@ -28,6 +27,7 @@ import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -45,11 +45,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val authenticationRepository = utils.authenticationRepository - private val authenticationInteractor = - utils.authenticationInteractor( - repository = authenticationRepository, - ) + private val authenticationInteractor = utils.authenticationInteractor() private val sceneInteractor = utils.sceneInteractor() private val bouncerInteractor = utils.bouncerInteractor( @@ -61,12 +57,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { authenticationInteractor = authenticationInteractor, actionButtonInteractor = utils.bouncerActionButtonInteractor(), ) + private val isInputEnabled = MutableStateFlow(true) private val underTest = PasswordBouncerViewModel( viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, - isInputEnabled = MutableStateFlow(true).asStateFlow(), + isInputEnabled.asStateFlow(), ) @Before @@ -123,8 +120,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenCorrect() = testScope.runTest { - val authResult by - collectLastValue(authenticationInteractor.authenticationChallengeResult) + val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult) lockDeviceAndOpenPasswordBouncer() underTest.onPasswordInputChanged("password") @@ -169,8 +165,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_correctAfterWrong() = testScope.runTest { - val authResult by - collectLastValue(authenticationInteractor.authenticationChallengeResult) + val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) lockDeviceAndOpenPasswordBouncer() @@ -333,19 +328,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { ) { if (isLockedOut) { repeat(failedAttemptCount) { - authenticationRepository.reportAuthenticationAttempt(false) + utils.authenticationRepository.reportAuthenticationAttempt(false) } - val remainingTimeSeconds = 30 - authenticationRepository.setLockoutDuration(remainingTimeSeconds * 1000) - authenticationRepository.lockout.value = - AuthenticationLockoutModel( - failedAttemptCount = failedAttemptCount, - remainingSeconds = remainingTimeSeconds, - ) + utils.authenticationRepository.reportLockoutStarted( + 30.seconds.inWholeMilliseconds.toInt() + ) } else { - authenticationRepository.reportAuthenticationAttempt(true) - authenticationRepository.lockout.value = null + utils.authenticationRepository.reportAuthenticationAttempt(true) } + isInputEnabled.value = !isLockedOut runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 8971423edd52..ed7609999804 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -111,8 +111,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenCorrect() = testScope.runTest { - val authResult by - collectLastValue(authenticationInteractor.authenticationChallengeResult) + val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) lockDeviceAndOpenPatternBouncer() @@ -334,8 +333,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_correctAfterWrong() = testScope.runTest { - val authResult by - collectLastValue(authenticationInteractor.authenticationChallengeResult) + val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index c30e405ab911..db98d7632910 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -201,8 +201,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenCorrect() = testScope.runTest { - val authResult by - collectLastValue(authenticationInteractor.authenticationChallengeResult) + val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult) lockDeviceAndOpenPinBouncer() FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked) @@ -236,8 +235,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_correctAfterWrong() = testScope.runTest { - val authResult by - collectLastValue(authenticationInteractor.authenticationChallengeResult) + val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) lockDeviceAndOpenPinBouncer() @@ -265,8 +263,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun onAutoConfirm_whenCorrect() = testScope.runTest { utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) - val authResult by - collectLastValue(authenticationInteractor.authenticationChallengeResult) + val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult) lockDeviceAndOpenPinBouncer() FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt index d3049d9080f3..565049baf6f4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt @@ -99,7 +99,7 @@ class DeviceEntryRepositoryTest : SysuiTestCase() { } @Test - fun reportSuccessfulAuthentication_shouldUpdateIsUnlocked() = + fun reportSuccessfulAuthentication_updatesIsUnlocked() = testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) assertThat(isUnlocked).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index 910097eece52..ea19cb799b10 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues @@ -346,12 +347,14 @@ class DeviceEntryInteractorTest : SysuiTestCase() { } @Test - fun successfulAuthenticationChallengeAttempt_updatedIsUnlockedState() = + fun successfulAuthenticationChallengeAttempt_updatesIsUnlockedState() = testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.deviceEntryRepository.setLockscreenEnabled(true) assertThat(isUnlocked).isFalse() - utils.authenticationRepository.reportAuthenticationAttempt(true) + authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) assertThat(isUnlocked).isTrue() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index c110de969b57..224903ff36b8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -265,8 +265,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { falsingCollector = utils.falsingCollector(), powerInteractor = powerInteractor, bouncerInteractor = bouncerInteractor, - simBouncerInteractor = utils.simBouncerInteractor, - authenticationInteractor = utils.authenticationInteractor() + simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor }, + authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() } ) startable.start() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 61d55f0ae667..2e4986dd1a10 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -90,8 +90,8 @@ class SceneContainerStartableTest : SysuiTestCase() { falsingCollector = falsingCollector, powerInteractor = powerInteractor, bouncerInteractor = bouncerInteractor, - simBouncerInteractor = utils.simBouncerInteractor, - authenticationInteractor = authenticationInteractor, + simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor }, + authenticationInteractor = dagger.Lazy { authenticationInteractor }, ) @Test diff --git a/packages/SystemUI/res/drawable/ic_memory.xml b/packages/SystemUI/res/drawable/ic_memory.xml deleted file mode 100644 index ada36c58ff1d..000000000000 --- a/packages/SystemUI/res/drawable/ic_memory.xml +++ /dev/null @@ -1,28 +0,0 @@ -<!-- -Copyright (C) 2018 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:pathData="M16.0,5.0l-8.0,0.0l0.0,14.0l8.0,0.0z" - android:fillAlpha="0.5" - android:fillColor="#000000"/> - <path - android:pathData="M6,9 L6,7 L4,7 L4,5 L6,5 C6,3.9 6.9,3 8,3 L16,3 C17.1,3 18,3.9 18,5 L20,5 L20,7 L18,7 L18,9 L20,9 L20,11 L18,11 L18,13 L20,13 L20,15 L18,15 L18,17 L20,17 L20,19 L18,19 C18,20.1 17.1,21 16,21 L8,21 C6.9,21 6,20.1 6,19 L4,19 L4,17 L6,17 L6,15 L4,15 L4,13 L6,13 L6,11 L4,11 L4,9 L6,9 Z M16,19 L16,5 L8,5 L8,19 L16,19 Z" - android:fillColor="#000000"/> -</vector> diff --git a/packages/SystemUI/res/layout/widget_picker.xml b/packages/SystemUI/res/layout/widget_picker.xml deleted file mode 100644 index 21dc224a6f14..000000000000 --- a/packages/SystemUI/res/layout/widget_picker.xml +++ /dev/null @@ -1,30 +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. - --> - -<HorizontalScrollView - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <LinearLayout - android:id="@+id/widgets_container" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:gravity="center_vertical" - android:orientation="horizontal"> - </LinearLayout> - -</HorizontalScrollView> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 90cc1fbb1fba..e01a2aa674b3 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -480,9 +480,6 @@ This name is in the ComponentName flattened format (package/class) --> <string name="config_remoteCopyPackage" translatable="false"></string> - <!-- On debuggable builds, alert the user if SystemUI PSS goes over this number (in kb) --> - <integer name="watch_heap_limit">256000</integer> - <!-- SystemUI Plugins that can be loaded on user builds. --> <string-array name="config_pluginAllowlist" translatable="false"> <item>com.android.systemui</item> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 0267454c9161..ee89edefcdbf 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -274,6 +274,10 @@ <!-- Side padding on the side of notifications --> <dimen name="notification_side_paddings">16dp</dimen> + <!-- Starting translateY offset of the HUN appear and disappear animations. Indicates + the amount by the view is positioned above the screen before the animation starts. --> + <dimen name="heads_up_appear_y_above_screen">32dp</dimen> + <!-- padding between the heads up and the statusbar --> <dimen name="heads_up_status_bar_padding">8dp</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 6cb2d457daea..d511caba941b 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -262,4 +262,8 @@ <!--Id for the device-entry UDFPS icon that lives in the alternate bouncer. --> <item type="id" name="alternate_bouncer_udfps_icon_view" /> + + <!-- Id for the udfps accessibility overlay --> + <item type="id" name="udfps_accessibility_overlay" /> + <item type="id" name="udfps_accessibility_overlay_top_guideline" /> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e10925d551e2..f4b25a701825 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2397,10 +2397,6 @@ <!-- URl of the webpage that explains battery saver. --> <string name="help_uri_battery_saver_learn_more_link_target" translatable="false"></string> - <!-- Name for a quick settings tile, used only by platform developers, to extract the SystemUI process memory and send it to another - app for debugging. Will not be seen by users. [CHAR LIMIT=20] --> - <string name="heap_dump_tile_name">Dump SysUI Heap</string> - <!-- Title for the privacy indicators dialog, only appears as part of a11y descriptions [CHAR LIMIT=NONE] --> <string name="ongoing_privacy_dialog_a11y_title">In use</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index d8c1e41465db..131eb6b63df3 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -367,7 +367,7 @@ public class QuickStepContract { /** * Corner radius that should be used on windows in order to cover the display. * These values are expressed in pixels because they should not respect display or font - * scaling, this means that we don't have to reload them on config changes. + * scaling. The corner radius may change when folding/unfolding the device. */ public static float getWindowCornerRadius(Context context) { return ScreenDecorationsUtils.getWindowCornerRadius(context); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index cdd7b804fdf8..74b975cb7232 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -39,7 +39,6 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.systemui.Dumpable; -import com.android.systemui.common.ui.ConfigurationState; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; @@ -48,9 +47,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; -import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder; import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager; -import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.log.dagger.KeyguardClockLog; @@ -62,17 +59,11 @@ import com.android.systemui.shared.regionsampling.RegionSampler; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.AnimationProperties; -import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; -import com.android.systemui.statusbar.phone.ScreenOffAnimationController; -import com.android.systemui.statusbar.ui.SystemBarUtilsState; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.SecureSettings; @@ -102,14 +93,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final DumpManager mDumpManager; private final ClockEventController mClockEventController; private final LogBuffer mLogBuffer; - private final NotificationIconContainerAlwaysOnDisplayViewModel mAodIconsViewModel; - private final KeyguardRootViewModel mKeyguardRootViewModel; - private final ConfigurationState mConfigurationState; - private final SystemBarUtilsState mSystemBarUtilsState; - private final DozeParameters mDozeParameters; - private final ScreenOffAnimationController mScreenOffAnimationController; - private final AlwaysOnDisplayNotificationIconViewStore mAodIconViewStore; - private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker; + private final NotificationIconContainerAlwaysOnDisplayViewBinder mNicViewBinder; private FrameLayout mSmallClockFrame; // top aligned clock private FrameLayout mLargeClockFrame; // centered clock @@ -183,9 +167,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS KeyguardSliceViewController keyguardSliceViewController, NotificationIconAreaController notificationIconAreaController, LockscreenSmartspaceController smartspaceController, - SystemBarUtilsState systemBarUtilsState, - ScreenOffAnimationController screenOffAnimationController, - StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker, + NotificationIconContainerAlwaysOnDisplayViewBinder nicViewBinder, KeyguardUnlockAnimationController keyguardUnlockAnimationController, SecureSettings secureSettings, @Main DelayableExecutor uiExecutor, @@ -193,11 +175,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS DumpManager dumpManager, ClockEventController clockEventController, @KeyguardClockLog LogBuffer logBuffer, - NotificationIconContainerAlwaysOnDisplayViewModel aodIconsViewModel, - KeyguardRootViewModel keyguardRootViewModel, - ConfigurationState configurationState, - DozeParameters dozeParameters, - AlwaysOnDisplayNotificationIconViewStore aodIconViewStore, KeyguardInteractor keyguardInteractor, KeyguardClockInteractor keyguardClockInteractor, FeatureFlagsClassic featureFlags, @@ -208,9 +185,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mKeyguardSliceViewController = keyguardSliceViewController; mNotificationIconAreaController = notificationIconAreaController; mSmartspaceController = smartspaceController; - mSystemBarUtilsState = systemBarUtilsState; - mScreenOffAnimationController = screenOffAnimationController; - mIconViewBindingFailureTracker = iconViewBindingFailureTracker; + mNicViewBinder = nicViewBinder; mSecureSettings = secureSettings; mUiExecutor = uiExecutor; mBgExecutor = bgExecutor; @@ -218,11 +193,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mDumpManager = dumpManager; mClockEventController = clockEventController; mLogBuffer = logBuffer; - mAodIconsViewModel = aodIconsViewModel; - mKeyguardRootViewModel = keyguardRootViewModel; - mConfigurationState = configurationState; - mDozeParameters = dozeParameters; - mAodIconViewStore = aodIconViewStore; mView.setLogBuffer(mLogBuffer); mFeatureFlags = featureFlags; mKeyguardInteractor = keyguardInteractor; @@ -619,28 +589,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mAodIconsBindHandle.dispose(); } if (nic != null) { - final DisposableHandle viewHandle = - NotificationIconContainerViewBinder.bindWhileAttached( - nic, - mAodIconsViewModel, - mConfigurationState, - mSystemBarUtilsState, - mIconViewBindingFailureTracker, - mAodIconViewStore); - final DisposableHandle visHandle = KeyguardRootViewBinder.bindAodIconVisibility( - nic, - mKeyguardRootViewModel.isNotifIconContainerVisible(), - mConfigurationState, - mFeatureFlags, - mScreenOffAnimationController); - if (visHandle == null) { - mAodIconsBindHandle = viewHandle; - } else { - mAodIconsBindHandle = () -> { - viewHandle.dispose(); - visHandle.dispose(); - }; - } + mAodIconsBindHandle = mNicViewBinder.bindWhileAttached(nic); mAodIconContainer = nic; } } else { diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index fda23b7f2a9c..f2b55f456f00 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -24,13 +24,13 @@ import android.os.UserHandle import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel -import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationResultModel import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise @@ -43,9 +43,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine @@ -60,12 +58,6 @@ import kotlinx.coroutines.withContext /** Defines interface for classes that can access authentication-related application state. */ interface AuthenticationRepository { /** - * Emits the result whenever a PIN/Pattern/Password security challenge is attempted by the user - * in order to unlock the device. - */ - val authenticationChallengeResult: SharedFlow<Boolean> - - /** * The exact length a PIN should be for us to enable PIN length hinting. * * A PIN that's shorter or longer than this is not eligible for the UI to render hints showing @@ -80,16 +72,6 @@ interface AuthenticationRepository { val isPatternVisible: StateFlow<Boolean> /** - * The current authentication lockout (aka "throttling") state, set when the user has to wait - * before being able to try another authentication attempt. `null` indicates throttling isn't - * active. - */ - val lockout: MutableStateFlow<AuthenticationLockoutModel?> - - /** Whether throttling has occurred at least once since the last successful authentication. */ - val hasLockoutOccurred: MutableStateFlow<Boolean> - - /** * Whether the auto confirm feature is enabled for the currently-selected user. * * Note that the length of the PIN is also important to take into consideration, please see @@ -98,6 +80,28 @@ interface AuthenticationRepository { val isAutoConfirmFeatureEnabled: StateFlow<Boolean> /** + * The number of failed authentication attempts for the selected user since their last + * successful authentication. + */ + val failedAuthenticationAttempts: StateFlow<Int> + + /** + * Timestamp for when the current lockout (aka "throttling") will end, allowing the user to + * attempt authentication again. Returns `null` if no lockout is active. + * + * Note that the value is in milliseconds and matches [SystemClock.elapsedRealtime]. + * + * Also note that the value may change when the selected user is changed. + */ + val lockoutEndTimestamp: Long? + + /** + * Whether lockout has occurred at least once since the last successful authentication of any + * user. + */ + val hasLockoutOccurred: StateFlow<Boolean> + + /** * The currently-configured authentication method. This determines how the authentication * challenge needs to be completed in order to unlock an otherwise locked device. * @@ -142,23 +146,6 @@ interface AuthenticationRepository { /** Reports that the user has entered a temporary device lockout (throttling). */ suspend fun reportLockoutStarted(durationMs: Int) - /** Returns the current number of failed authentication attempts. */ - suspend fun getFailedAuthenticationAttemptCount(): Int - - /** - * Returns the timestamp for when the current lockout will end, allowing the user to attempt - * authentication again. - * - * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime]. - */ - suspend fun getLockoutEndTimestamp(): Long - - /** - * Sets the lockout timeout duration (time during which the user should not be allowed to - * attempt authentication). - */ - suspend fun setLockoutDuration(durationMs: Int) - /** * Checks the given [LockscreenCredential] to see if it's correct, returning an * [AuthenticationResultModel] representing what happened. @@ -172,6 +159,8 @@ class AuthenticationRepositoryImpl constructor( @Application private val applicationScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, + flags: SceneContainerFlags, + private val clock: SystemClock, private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>, private val userRepository: UserRepository, private val lockPatternUtils: LockPatternUtils, @@ -179,8 +168,6 @@ constructor( mobileConnectionsRepository: MobileConnectionsRepository, ) : AuthenticationRepository { - override val authenticationChallengeResult = MutableSharedFlow<Boolean>() - override val hintedPinLength: Int = 6 override val isPatternVisible: StateFlow<Boolean> = @@ -189,10 +176,6 @@ constructor( getFreshValue = lockPatternUtils::isVisiblePatternEnabled, ) - override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null) - - override val hasLockoutOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false) - override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = refreshingFlow( initialValue = false, @@ -234,6 +217,31 @@ constructor( getFreshValue = { userId -> lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) }, ) + private val _failedAuthenticationAttempts = MutableStateFlow(0) + override val failedAuthenticationAttempts: StateFlow<Int> = + _failedAuthenticationAttempts.asStateFlow() + + override val lockoutEndTimestamp: Long? + get() = + lockPatternUtils.getLockoutAttemptDeadline(selectedUserId).takeIf { + clock.elapsedRealtime() < it + } + + private val _hasLockoutOccurred = MutableStateFlow(false) + override val hasLockoutOccurred: StateFlow<Boolean> = _hasLockoutOccurred.asStateFlow() + + init { + if (flags.isEnabled()) { + // Hydrate failedAuthenticationAttempts initially and whenever the selected user + // changes. + applicationScope.launch { + userRepository.selectedUserInfo.collect { + _failedAuthenticationAttempts.value = getFailedAuthenticationAttemptCount() + } + } + } + } + override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return withContext(backgroundDispatcher) { blockingAuthenticationMethodInternal(selectedUserId) @@ -248,35 +256,20 @@ constructor( withContext(backgroundDispatcher) { if (isSuccessful) { lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId) + _hasLockoutOccurred.value = false } else { lockPatternUtils.reportFailedPasswordAttempt(selectedUserId) } - authenticationChallengeResult.emit(isSuccessful) + _failedAuthenticationAttempts.value = getFailedAuthenticationAttemptCount() } } override suspend fun reportLockoutStarted(durationMs: Int) { - return withContext(backgroundDispatcher) { - lockPatternUtils.reportPasswordLockout(durationMs, selectedUserId) - } - } - - override suspend fun getFailedAuthenticationAttemptCount(): Int { - return withContext(backgroundDispatcher) { - lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId) - } - } - - override suspend fun getLockoutEndTimestamp(): Long { - return withContext(backgroundDispatcher) { - lockPatternUtils.getLockoutAttemptDeadline(selectedUserId) - } - } - - override suspend fun setLockoutDuration(durationMs: Int) { + lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs) withContext(backgroundDispatcher) { - lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs) + lockPatternUtils.reportPasswordLockout(durationMs, selectedUserId) } + _hasLockoutOccurred.value = true } override suspend fun checkCredential( @@ -292,6 +285,12 @@ constructor( } } + private suspend fun getFailedAuthenticationAttemptCount(): Int { + return withContext(backgroundDispatcher) { + lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId) + } + } + private val selectedUserId: Int get() = userRepository.getSelectedUserInfo().id diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 797154e85082..c85ffe6ca56f 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -16,36 +16,25 @@ package com.android.systemui.authentication.domain.interactor -import com.android.app.tracing.TraceUtils.Companion.withContext import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential import com.android.systemui.authentication.data.repository.AuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.time.SystemClock import javax.inject.Inject -import kotlin.math.ceil -import kotlin.math.max -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.async -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch /** * Hosts application business logic related to user authentication. @@ -59,10 +48,7 @@ class AuthenticationInteractor @Inject constructor( @Application private val applicationScope: CoroutineScope, - @Background private val backgroundDispatcher: CoroutineDispatcher, private val repository: AuthenticationRepository, - private val userRepository: UserRepository, - private val clock: SystemClock, ) { /** * The currently-configured authentication method. This determines how the authentication @@ -85,13 +71,6 @@ constructor( val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod /** - * The current authentication lockout (aka "throttling") state, set when the user has to wait - * before being able to try another authentication attempt. `null` indicates lockout isn't - * active. - */ - val lockout: StateFlow<AuthenticationLockoutModel?> = repository.lockout - - /** * Whether the auto confirm feature is enabled for the currently-selected user. * * Note that the length of the PIN is also important to take into consideration, please see @@ -130,26 +109,35 @@ constructor( /** Whether the pattern should be visible for the currently-selected user. */ val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible + private val _onAuthenticationResult = MutableSharedFlow<Boolean>() /** * Emits the outcome (successful or unsuccessful) whenever a PIN/Pattern/Password security * challenge is attempted by the user in order to unlock the device. */ - val authenticationChallengeResult: SharedFlow<Boolean> = - repository.authenticationChallengeResult + val onAuthenticationResult: SharedFlow<Boolean> = _onAuthenticationResult.asSharedFlow() /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled - private var lockoutCountdownJob: Job? = null + /** + * The number of failed authentication attempts for the selected user since the last successful + * authentication. + */ + val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts - init { - applicationScope.launch { - userRepository.selectedUserInfo - .map { it.id } - .distinctUntilChanged() - .collect { onSelectedUserChanged() } - } - } + /** + * Timestamp for when the current lockout (aka "throttling") will end, allowing the user to + * attempt authentication again. Returns `null` if no lockout is active. + * + * To be notified whenever a lockout is started, the caller should subscribe to + * [onAuthenticationResult]. + * + * Note that the value is in milliseconds and matches [SystemClock.elapsedRealtime]. + * + * Also note that the value may change when the selected user is changed. + */ + val lockoutEndTimestamp: Long? + get() = repository.lockoutEndTimestamp /** * Returns the currently-configured authentication method. This determines how the @@ -190,7 +178,7 @@ constructor( val skipCheck = when { // Lockout is active, the UI layer should not have called this; skip the attempt. - lockout.value != null -> true + repository.lockoutEndTimestamp != null -> true // The input is too short; skip the attempt. input.isTooShort(authMethod) -> true // Auto-confirm attempt when the feature is not enabled; skip the attempt. @@ -211,27 +199,16 @@ constructor( credential.zeroize() if (authenticationResult.isSuccessful || !tryAutoConfirm) { - repository.reportAuthenticationAttempt( - isSuccessful = authenticationResult.isSuccessful, - ) + repository.reportAuthenticationAttempt(authenticationResult.isSuccessful) } // Check if lockout should start and, if so, kick off the countdown: if (!authenticationResult.isSuccessful && authenticationResult.lockoutDurationMs > 0) { - repository.apply { - setLockoutDuration(durationMs = authenticationResult.lockoutDurationMs) - reportLockoutStarted(durationMs = authenticationResult.lockoutDurationMs) - hasLockoutOccurred.value = true - } - startLockoutCountdown() + repository.reportLockoutStarted(authenticationResult.lockoutDurationMs) } - if (authenticationResult.isSuccessful) { - // Since authentication succeeded, refresh lockout to make sure the state is completely - // reflecting the upstream source of truth. - refreshLockout() - - repository.hasLockoutOccurred.value = false + if (authenticationResult.isSuccessful || !tryAutoConfirm) { + _onAuthenticationResult.emit(authenticationResult.isSuccessful) } return if (authenticationResult.isSuccessful) { @@ -249,54 +226,6 @@ constructor( } } - /** Starts refreshing the lockout state every second. */ - private suspend fun startLockoutCountdown() { - cancelLockoutCountdown() - lockoutCountdownJob = - applicationScope.launch { - while (refreshLockout()) { - delay(1.seconds.inWholeMilliseconds) - } - } - } - - /** Cancels any lockout state countdown started in [startLockoutCountdown]. */ - private fun cancelLockoutCountdown() { - lockoutCountdownJob?.cancel() - lockoutCountdownJob = null - } - - /** Notifies that the currently-selected user has changed. */ - private suspend fun onSelectedUserChanged() { - cancelLockoutCountdown() - if (refreshLockout()) { - startLockoutCountdown() - } - } - - /** - * Refreshes the lockout state, hydrating the repository with the latest state. - * - * @return Whether lockout is active or not. - */ - private suspend fun refreshLockout(): Boolean { - withContext("$TAG#refreshLockout", backgroundDispatcher) { - val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() } - val deadline = async { repository.getLockoutEndTimestamp() } - val remainingMs = max(0, deadline.await() - clock.elapsedRealtime()) - repository.lockout.value = - if (remainingMs > 0) { - AuthenticationLockoutModel( - failedAttemptCount = failedAttemptCount.await(), - remainingSeconds = ceil(remainingMs / 1000f).toInt(), - ) - } else { - null // Lockout ended. - } - } - return repository.lockout.value != null - } - private fun AuthenticationMethodModel.createCredential( input: List<Any> ): LockscreenCredential? { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 65668b56a9f3..240728a8f1e8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -86,8 +86,6 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; -import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter; -import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -115,7 +113,6 @@ import java.util.Set; import java.util.concurrent.Executor; import javax.inject.Inject; -import javax.inject.Provider; import kotlinx.coroutines.ExperimentalCoroutinesApi; @@ -152,7 +149,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final SystemUIDialogManager mDialogManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; - @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; @NonNull private final VibratorHelper mVibrator; @NonNull private final FalsingManager mFalsingManager; @NonNull private final PowerManager mPowerManager; @@ -623,7 +619,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } else { onKeyguard = mOverlay != null && mOverlay.getAnimationViewController() - instanceof UdfpsKeyguardViewControllerAdapter; + instanceof UdfpsKeyguardViewControllerLegacy; } return onKeyguard && mKeyguardStateController.canDismissLockScreen() @@ -666,7 +662,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull InputManager inputManager, @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate, - @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider, @NonNull SelectedUserInteractor selectedUserInteractor, @NonNull FpsUnlockTracker fpsUnlockTracker, @NonNull KeyguardTransitionInteractor keyguardTransitionInteractor, @@ -737,7 +732,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { return Unit.INSTANCE; }); mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; - mUdfpsKeyguardViewModels = udfpsKeyguardViewModelsProvider; final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController(); mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index dae6d08f7331..aabee93fd0a0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -55,7 +55,6 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -438,7 +437,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( if (DeviceEntryUdfpsRefactor.isEnabled) { !keyguardStateController.isShowing } else { - animation !is UdfpsKeyguardViewControllerAdapter + animation !is UdfpsKeyguardViewControllerLegacy } if (keyguardNotShowing) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 63fe26a37e46..64148f6035e0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -33,7 +33,6 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R @@ -81,8 +80,7 @@ open class UdfpsKeyguardViewControllerLegacy( primaryBouncerInteractor, systemUIDialogManager, dumpManager, - ), - UdfpsKeyguardViewControllerAdapter { + ) { private val uniqueIdentifier = this.toString() private var showingUdfpsBouncer = false private var udfpsRequested = false @@ -199,11 +197,27 @@ open class UdfpsKeyguardViewControllerLegacy( listenForAodToOccludedTransitions(this) listenForAlternateBouncerToAodTransitions(this) listenForDreamingToAodTransitions(this) + listenForPrimaryBouncerToAodTransitions(this) } } } @VisibleForTesting + suspend fun listenForPrimaryBouncerToAodTransitions(scope: CoroutineScope): Job { + return scope.launch { + transitionInteractor + .transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD) + .collect { transitionStep -> + view.onDozeAmountChanged( + transitionStep.value, + transitionStep.value, + ANIMATE_APPEAR_ON_SCREEN_OFF, + ) + } + } + } + + @VisibleForTesting suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job { return scope.launch { transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt deleted file mode 100644 index 6f4e1a3cae46..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt +++ /dev/null @@ -1,78 +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.biometrics.ui.controller - -import com.android.systemui.biometrics.UdfpsAnimationViewController -import com.android.systemui.biometrics.UdfpsKeyguardView -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor -import com.android.systemui.dump.DumpManager -import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter -import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.statusbar.phone.SystemUIDialogManager -import kotlinx.coroutines.ExperimentalCoroutinesApi - -/** Class that coordinates non-HBM animations during keyguard authentication. */ -@ExperimentalCoroutinesApi -open class UdfpsKeyguardViewController( - val view: UdfpsKeyguardView, - statusBarStateController: StatusBarStateController, - primaryBouncerInteractor: PrimaryBouncerInteractor, - systemUIDialogManager: SystemUIDialogManager, - dumpManager: DumpManager, - private val alternateBouncerInteractor: AlternateBouncerInteractor, - udfpsKeyguardViewModels: UdfpsKeyguardViewModels, -) : - UdfpsAnimationViewController<UdfpsKeyguardView>( - view, - statusBarStateController, - primaryBouncerInteractor, - systemUIDialogManager, - dumpManager, - ), - UdfpsKeyguardViewControllerAdapter { - private val uniqueIdentifier = this.toString() - override val tag: String - get() = TAG - - init { - udfpsKeyguardViewModels.bindViews(view) - } - - public override fun onViewAttached() { - super.onViewAttached() - alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier) - } - - public override fun onViewDetached() { - super.onViewDetached() - alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier) - } - - override fun shouldPauseAuth(): Boolean { - return !view.isVisible() - } - - override fun listenForTouchesOutsideView(): Boolean { - return true - } - - companion object { - private const val TAG = "UdfpsKeyguardViewController" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 724c0fe1e4e4..1095abe24b47 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -19,7 +19,6 @@ package com.android.systemui.bouncer.domain.interactor import android.content.Context import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.domain.interactor.AuthenticationResult -import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.classifier.FalsingClassifier @@ -29,17 +28,15 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R -import com.android.systemui.scene.shared.flag.SceneContainerFlags -import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch /** Encapsulates business logic and application state accessing use-cases. */ @@ -52,33 +49,12 @@ constructor( private val repository: BouncerRepository, private val authenticationInteractor: AuthenticationInteractor, private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor, - flags: SceneContainerFlags, private val falsingInteractor: FalsingInteractor, private val powerInteractor: PowerInteractor, private val simBouncerInteractor: SimBouncerInteractor, ) { - - /** The user-facing message to show in the bouncer. */ - val message: StateFlow<String?> = - combine(repository.message, authenticationInteractor.lockout) { message, lockout -> - messageOrLockoutMessage(message, lockout) - } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = - messageOrLockoutMessage( - repository.message.value, - authenticationInteractor.lockout.value, - ) - ) - - /** - * The current authentication lockout (aka "throttling") state, set when the user has to wait - * before being able to try another authentication attempt. `null` indicates lockout isn't - * active. - */ - val lockout: StateFlow<AuthenticationLockoutModel?> = authenticationInteractor.lockout + /** The user-facing message to show in the bouncer when lockout is not active. */ + val message: StateFlow<String?> = repository.message /** Whether the auto confirm feature is enabled for the currently-selected user. */ val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled @@ -101,18 +77,13 @@ constructor( /** Emits a [Unit] each time the IME (keyboard) is hidden by the user. */ val onImeHiddenByUser: SharedFlow<Unit> = _onImeHiddenByUser - init { - if (flags.isEnabled()) { - // Clear the message if moved from locked-out to no-longer locked-out. - applicationScope.launch { - lockout.pairwise().collect { (previous, current) -> - if (previous != null && current == null) { - clearMessage() - } - } + /** Emits a [Unit] each time a lockout is started for the selected user. */ + val onLockoutStarted: Flow<Unit> = + authenticationInteractor.onAuthenticationResult + .filter { successfullyAuthenticated -> + !successfullyAuthenticated && authenticationInteractor.lockoutEndTimestamp != null } - } - } + .map {} /** Notifies that the user has places down a pointer, not necessarily dragging just yet. */ fun onDown() { @@ -188,7 +159,7 @@ constructor( } if (authenticationInteractor.getAuthenticationMethod() == AuthenticationMethodModel.Sim) { - // We authenticate sim in SimInteractor + // SIM is authenticated in SimBouncerInteractor. return AuthenticationResult.SKIPPED } @@ -196,18 +167,20 @@ constructor( // view-models, whose lifecycle (and thus scope) is shorter than this interactor. // This allows the task to continue running properly even when the calling scope has been // cancelled. - return applicationScope - .async { - val authResult = authenticationInteractor.authenticate(input, tryAutoConfirm) - if ( - authResult == AuthenticationResult.FAILED || - (authResult == AuthenticationResult.SKIPPED && !tryAutoConfirm) - ) { - showErrorMessage() - } - authResult - } - .await() + val authResult = + applicationScope + .async { authenticationInteractor.authenticate(input, tryAutoConfirm) } + .await() + + if (authenticationInteractor.lockoutEndTimestamp != null) { + clearMessage() + } else if ( + authResult == AuthenticationResult.FAILED || + (authResult == AuthenticationResult.SKIPPED && !tryAutoConfirm) + ) { + showErrorMessage() + } + return authResult } /** @@ -250,19 +223,4 @@ constructor( else -> "" } } - - private fun messageOrLockoutMessage( - message: String?, - lockoutModel: AuthenticationLockoutModel?, - ): String { - return when { - lockoutModel != null -> - applicationContext.getString( - com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown, - lockoutModel.remainingSeconds, - ) - message != null -> message - else -> "" - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 4b1434323886..be6cf85a5a0e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import android.graphics.Bitmap import androidx.core.graphics.drawable.toBitmap +import com.android.internal.R import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor @@ -34,19 +35,24 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.user.ui.viewmodel.UserActionViewModel import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel import com.android.systemui.user.ui.viewmodel.UserViewModel +import com.android.systemui.util.time.SystemClock import dagger.Module import dagger.Provides +import kotlin.math.ceil +import kotlin.math.max +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.job @@ -58,13 +64,14 @@ class BouncerViewModel( @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, private val bouncerInteractor: BouncerInteractor, - authenticationInteractor: AuthenticationInteractor, + private val authenticationInteractor: AuthenticationInteractor, flags: SceneContainerFlags, selectedUser: Flow<UserViewModel>, users: Flow<List<UserViewModel>>, userSwitcherMenu: Flow<List<UserActionViewModel>>, actionButtonInteractor: BouncerActionButtonInteractor, private val simBouncerInteractor: SimBouncerInteractor, + private val clock: SystemClock, ) { val selectedUserImage: StateFlow<Bitmap?> = selectedUser @@ -104,15 +111,6 @@ class BouncerViewModel( val isUserSwitcherVisible: Boolean get() = bouncerInteractor.isUserSwitcherVisible - private val isInputEnabled: StateFlow<Boolean> = - bouncerInteractor.lockout - .map { it == null } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = bouncerInteractor.lockout.value == null, - ) - // Handle to the scope of the child ViewModel (stored in [authMethod]). private var childViewModelScope: CoroutineScope? = null private val _dialogMessage = MutableStateFlow<String?>(null) @@ -138,19 +136,22 @@ class BouncerViewModel( */ val dialogMessage: StateFlow<String?> = _dialogMessage.asStateFlow() + /** + * A message shown when the user has attempted the wrong credential too many times and now must + * wait a while before attempting to authenticate again. + * + * This is updated every second (countdown) during the lockout duration. When lockout is not + * active, this is `null` and no lockout message should be shown. + */ + private val lockoutMessage = MutableStateFlow<String?>(null) + /** The user-facing message to show in the bouncer. */ val message: StateFlow<MessageViewModel> = - combine(bouncerInteractor.message, bouncerInteractor.lockout) { message, lockout -> - toMessageViewModel(message, isLockedOut = lockout != null) - } + combine(bouncerInteractor.message, lockoutMessage) { _, _ -> createMessageViewModel() } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = - toMessageViewModel( - message = bouncerInteractor.message.value, - isLockedOut = bouncerInteractor.lockout.value != null, - ), + initialValue = createMessageViewModel(), ) /** @@ -194,31 +195,78 @@ class BouncerViewModel( initialValue = isFoldSplitRequired(authMethodViewModel.value), ) + private val isInputEnabled: StateFlow<Boolean> = + lockoutMessage + .map { it == null } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = authenticationInteractor.lockoutEndTimestamp == null, + ) + + private var lockoutCountdownJob: Job? = null + init { if (flags.isEnabled()) { applicationScope.launch { - combine(bouncerInteractor.lockout, authMethodViewModel) { - lockout, - authMethodViewModel -> - if (lockout != null && authMethodViewModel != null) { + bouncerInteractor.onLockoutStarted.collect { + showLockoutDialog() + startLockoutCountdown() + } + } + + applicationScope.launch { + // Update the lockout countdown whenever the selected user is switched. + selectedUser.collect { startLockoutCountdown() } + } + } + } + + /** Notifies that the dialog has been dismissed by the user. */ + fun onDialogDismissed() { + _dialogMessage.value = null + } + + private fun showLockoutDialog() { + applicationScope.launch { + val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value + _dialogMessage.value = + authMethodViewModel.value?.lockoutMessageId?.let { messageId -> + applicationContext.getString( + messageId, + failedAttempts, + remainingLockoutSeconds() + ) + } + } + } + + /** Shows the countdown message and refreshes it every second. */ + private fun startLockoutCountdown() { + lockoutCountdownJob?.cancel() + lockoutCountdownJob = + applicationScope.launch { + do { + val remainingSeconds = remainingLockoutSeconds() + lockoutMessage.value = + if (remainingSeconds > 0) { applicationContext.getString( - authMethodViewModel.lockoutMessageId, - lockout.failedAttemptCount, - lockout.remainingSeconds, + R.string.lockscreen_too_many_failed_attempts_countdown, + remainingSeconds, ) } else { null } - } - .distinctUntilChanged() - .collect { dialogMessage -> _dialogMessage.value = dialogMessage } + delay(1.seconds) + } while (remainingSeconds > 0) + lockoutCountdownJob = null } - } } - /** Notifies that the dialog has been dismissed by the user. */ - fun onDialogDismissed() { - _dialogMessage.value = null + private fun remainingLockoutSeconds(): Int { + val endTimestampMs = authenticationInteractor.lockoutEndTimestamp ?: 0 + val remainingMs = max(0, endTimestampMs - clock.elapsedRealtime()) + return ceil(remainingMs / 1000f).toInt() } private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean { @@ -229,12 +277,11 @@ class BouncerViewModel( return authMethod !is PasswordBouncerViewModel } - private fun toMessageViewModel( - message: String?, - isLockedOut: Boolean, - ): MessageViewModel { + private fun createMessageViewModel(): MessageViewModel { + val isLockedOut = lockoutMessage.value != null return MessageViewModel( - text = message ?: "", + // A lockout message takes precedence over the non-lockout message. + text = lockoutMessage.value ?: bouncerInteractor.message.value ?: "", isUpdateAnimated = !isLockedOut, ) } @@ -328,6 +375,7 @@ object BouncerViewModelModule { userSwitcherViewModel: UserSwitcherViewModel, actionButtonInteractor: BouncerActionButtonInteractor, simBouncerInteractor: SimBouncerInteractor, + clock: SystemClock, ): BouncerViewModel { return BouncerViewModel( applicationContext = applicationContext, @@ -341,6 +389,7 @@ object BouncerViewModelModule { userSwitcherMenu = userSwitcherViewModel.menu, actionButtonInteractor = actionButtonInteractor, simBouncerInteractor = simBouncerInteractor, + clock = clock, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index b68271767fc2..8e14778f1aa7 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -56,13 +56,11 @@ class PasswordBouncerViewModel( /** Whether the UI should request focus on the text field element. */ val isTextFieldFocusRequested = - combine(interactor.lockout, isTextFieldFocused) { throttling, hasFocus -> - throttling == null && !hasFocus - } + combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> hasInput && !hasFocus } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(), - initialValue = interactor.lockout.value == null && !isTextFieldFocused.value, + initialValue = isInputEnabled.value && !isTextFieldFocused.value, ) override fun onHidden() { @@ -104,7 +102,7 @@ class PasswordBouncerViewModel( * hidden. */ suspend fun onImeVisibilityChanged(isVisible: Boolean) { - if (isImeVisible && !isVisible && interactor.lockout.value == null) { + if (isImeVisible && !isVisible && isInputEnabled.value) { interactor.onImeHiddenByUser() } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 887b18cfe4c9..0a13e4887ebe 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -16,8 +16,9 @@ package com.android.systemui.communal.widgets -import android.appwidget.AppWidgetProviderInfo +import android.content.ComponentName import android.content.Intent +import android.content.pm.PackageManager import android.os.Bundle import android.os.RemoteException import android.util.Log @@ -39,10 +40,8 @@ constructor( private var windowManagerService: IWindowManager? = null, ) : ComponentActivity() { companion object { - /** - * Intent extra name for the {@link AppWidgetProviderInfo} of a widget to add to hub mode. - */ - const val ADD_WIDGET_INFO = "add_widget_info" + private const val EXTRA_FILTER_STRATEGY = "filter_strategy" + private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1 private const val TAG = "EditWidgetsActivity" } @@ -51,13 +50,8 @@ constructor( when (result.resultCode) { RESULT_OK -> { result.data - ?.let { - it.getParcelableExtra( - ADD_WIDGET_INFO, - AppWidgetProviderInfo::class.java - ) - } - ?.let { communalInteractor.addWidget(it.provider, 0) } + ?.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java) + ?.let { communalInteractor.addWidget(it, 0) } ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") } } else -> @@ -71,13 +65,35 @@ constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setShowWhenLocked(true) + setCommunalEditWidgetActivityContent( activity = this, viewModel = communalViewModel, onOpenWidgetPicker = { - addWidgetActivityLauncher.launch( - Intent(applicationContext, WidgetPickerActivity::class.java) - ) + val localPackageManager: PackageManager = getPackageManager() + val intent = + Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) } + localPackageManager + .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) + ?.activityInfo + ?.packageName + ?.let { packageName -> + try { + addWidgetActivityLauncher.launch( + Intent(Intent.ACTION_PICK).also { + it.setPackage(packageName) + it.putExtra( + EXTRA_FILTER_STRATEGY, + FILTER_STRATEGY_GLANCEABLE_HUB + ) + } + ) + } catch (e: Exception) { + Log.e(TAG, "Failed to launch widget picker activity", e) + } + } + ?: run { Log.e(TAG, "Couldn't resolve launcher package name") } }, onEditDone = { try { diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt deleted file mode 100644 index a26afc86aa2e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt +++ /dev/null @@ -1,104 +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.communal.widgets - -import android.appwidget.AppWidgetManager -import android.appwidget.AppWidgetProviderInfo -import android.content.Intent -import android.graphics.Color -import android.os.Bundle -import android.util.Log -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.LinearLayout -import androidx.activity.ComponentActivity -import androidx.core.view.setMargins -import androidx.core.view.setPadding -import com.android.systemui.res.R -import javax.inject.Inject - -/** - * An Activity responsible for displaying a list of widgets to add to the hub mode grid. This is - * essentially a placeholder until Launcher's widget picker can be used. - */ -class WidgetPickerActivity -@Inject -constructor( - private val appWidgetManager: AppWidgetManager, -) : ComponentActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContentView(R.layout.widget_picker) - loadWidgets() - } - - private fun loadWidgets() { - val containerView: ViewGroup? = findViewById(R.id.widgets_container) - containerView?.apply { - try { - appWidgetManager - .getInstalledProviders(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) - ?.stream() - ?.forEach { widgetInfo -> - val activity = this@WidgetPickerActivity - (widgetInfo.loadPreviewImage(activity, 0) - ?: widgetInfo.loadIcon(activity, 0)) - ?.let { - addView( - ImageView(activity).also { v -> - v.setImageDrawable(it) - v.setBackgroundColor(WIDGET_PREVIEW_BACKGROUND_COLOR) - v.setPadding(WIDGET_PREVIEW_PADDING) - v.layoutParams = - LinearLayout.LayoutParams( - WIDGET_PREVIEW_SIZE, - WIDGET_PREVIEW_SIZE - ) - .also { lp -> - lp.setMargins(WIDGET_PREVIEW_MARGINS) - } - v.setOnClickListener { - setResult( - RESULT_OK, - Intent() - .putExtra( - EditWidgetsActivity.ADD_WIDGET_INFO, - widgetInfo - ) - ) - finish() - } - } - ) - } - } - } catch (e: RuntimeException) { - Log.e(TAG, "Exception fetching widget providers", e) - } - } - } - - companion object { - private const val WIDGET_PREVIEW_SIZE = 600 - private const val WIDGET_PREVIEW_MARGINS = 32 - private const val WIDGET_PREVIEW_PADDING = 32 - private val WIDGET_PREVIEW_BACKGROUND_COLOR = Color.rgb(216, 225, 220) - private const val TAG = "WidgetPickerActivity" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index 4b27af1fc989..9afd5ede0b4c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -20,7 +20,6 @@ import android.app.Activity; import com.android.systemui.ForegroundServicesDialog; import com.android.systemui.communal.widgets.EditWidgetsActivity; -import com.android.systemui.communal.widgets.WidgetPickerActivity; import com.android.systemui.contrast.ContrastDialogActivity; import com.android.systemui.keyguard.WorkLockActivity; import com.android.systemui.people.PeopleSpaceActivity; @@ -158,12 +157,6 @@ public abstract class DefaultActivityBinder { @ClassKey(EditWidgetsActivity.class) public abstract Activity bindEditWidgetsActivity(EditWidgetsActivity activity); - /** Inject into WidgetPickerActivity. */ - @Binds - @IntoMap - @ClassKey(WidgetPickerActivity.class) - public abstract Activity bindWidgetPickerActivity(WidgetPickerActivity activity); - /** Inject into SwitchToManagedProfileForCallActivity. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 47be8ab0c0a2..f6a9570fc94c 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -159,7 +159,7 @@ constructor( fun attemptDeviceEntry() { // TODO (b/307768356), // 1. Check if the device is already authenticated by trust agent/passive biometrics - // 2. show SPFS/UDFPS bouncer if it is available AlternateBouncerInteractor.show + // 2. Show SPFS/UDFPS bouncer if it is available AlternateBouncerInteractor.show // 3. For face auth only setups trigger face auth, delay transitioning to bouncer for // a small amount of time. // 4. Transition to bouncer scene @@ -197,8 +197,8 @@ constructor( init { if (flags.isEnabled()) { applicationScope.launch { - authenticationInteractor.authenticationChallengeResult.collectLatest { successful -> - if (successful) { + authenticationInteractor.onAuthenticationResult.collectLatest { isSuccessful -> + if (isSuccessful) { repository.reportSuccessfulAuthentication() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt index 0113628c30ca..ef2537c1bebb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt @@ -15,40 +15,32 @@ * */ -package com.android.systemui.keyguard.ui.binder +package com.android.systemui.deviceentry.ui.binder -import android.content.res.ColorStateList -import android.widget.ImageView +import android.annotation.SuppressLint +import androidx.core.view.isInvisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel +import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay +import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel import com.android.systemui.lifecycle.repeatWhenAttached import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch @ExperimentalCoroutinesApi -object UdfpsBackgroundViewBinder { +object UdfpsAccessibilityOverlayBinder { - /** - * Drives UI for the udfps background view. See [UdfpsAodFingerprintViewBinder] and - * [UdfpsFingerprintViewBinder]. - */ + /** Forwards hover events to the view model to make guided announcements for accessibility. */ + @SuppressLint("ClickableViewAccessibility") @JvmStatic fun bind( - view: ImageView, - viewModel: BackgroundViewModel, + view: UdfpsAccessibilityOverlay, + viewModel: UdfpsAccessibilityOverlayViewModel, ) { - view.alpha = 0f + view.setOnHoverListener { v, event -> viewModel.onHoverEvent(v, event) } view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.transition.collect { - view.alpha = it.alpha - view.scaleX = it.scale - view.scaleY = it.scale - view.imageTintList = ColorStateList.valueOf(it.color) - } - } + // Repeat on CREATED because we update the visibility of the view + repeatOnLifecycle(Lifecycle.State.CREATED) { + viewModel.visible.collect { visible -> view.isInvisible = !visible } } } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt index 7e35360e30ff..7be323073692 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt @@ -12,13 +12,12 @@ * 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.shared.notifications.shared.model +package com.android.systemui.deviceentry.ui.view + +import android.content.Context +import android.view.View -/** Models notification settings. */ -data class NotificationSettingsModel( - /** Whether notifications are shown on the lock screen. */ - val isShowNotificationsOnLockScreenEnabled: Boolean = false, -) +/** Overlay to handle under-fingerprint sensor accessibility events. */ +class UdfpsAccessibilityOverlay(context: Context?) : View(context) diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt new file mode 100644 index 000000000000..80684b442c5d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt @@ -0,0 +1,91 @@ +/* + * 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.deviceentry.ui.viewmodel + +import android.graphics.Point +import android.view.MotionEvent +import android.view.View +import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor +import com.android.systemui.biometrics.UdfpsUtils +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor +import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf + +/** Models the UI state for the UDFPS accessibility overlay */ +@ExperimentalCoroutinesApi +class UdfpsAccessibilityOverlayViewModel +@Inject +constructor( + udfpsOverlayInteractor: UdfpsOverlayInteractor, + accessibilityInteractor: AccessibilityInteractor, + deviceEntryIconViewModel: DeviceEntryIconViewModel, + deviceEntryFgIconViewModel: DeviceEntryForegroundViewModel, +) { + private val udfpsUtils = UdfpsUtils() + private val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> = + udfpsOverlayInteractor.udfpsOverlayParams + + /** Overlay is only visible if touch exploration is enabled and UDFPS can be used. */ + val visible: Flow<Boolean> = + accessibilityInteractor.isTouchExplorationEnabled.flatMapLatest { touchExplorationEnabled -> + if (touchExplorationEnabled) { + combine( + deviceEntryFgIconViewModel.viewModel, + deviceEntryIconViewModel.deviceEntryViewAlpha, + ) { iconViewModel, alpha -> + iconViewModel.type == DeviceEntryIconView.IconType.FINGERPRINT && + !iconViewModel.useAodVariant && + alpha == 1f + } + } else { + flowOf(false) + } + } + + /** Give directional feedback to help the user authenticate with UDFPS. */ + fun onHoverEvent(v: View, event: MotionEvent): Boolean { + val overlayParams = udfpsOverlayParams.value + val scaledTouch: Point = + udfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), event, overlayParams) + + if (!udfpsUtils.isWithinSensorArea(event.getPointerId(0), event, overlayParams)) { + // view only receives motionEvents when [visible] which requires touchExplorationEnabled + val announceStr = + udfpsUtils.onTouchOutsideOfSensorArea( + /* touchExplorationEnabled */ true, + v.context, + scaledTouch.x, + scaledTouch.y, + overlayParams, + ) + if (announceStr != null) { + v.announceForAccessibility(announceStr) + } + } + // always let the motion events go through to underlying views + return false + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt index 6560ee3408d4..14fda5e96a9c 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt @@ -73,9 +73,19 @@ abstract class FlagDependenciesBase( ) { val isMet = !alphaEnabled || betaEnabled override fun toString(): String { - val isMetBullet = if (isMet) "+" else "-" - return "$isMetBullet $alphaName ($alphaEnabled) DEPENDS ON $betaName ($betaEnabled)" + val prefix = + when { + !isMet -> " [NOT MET]" + alphaEnabled -> " [met]" + betaEnabled -> " [ready]" + else -> "[not ready]" + } + val alphaState = if (alphaEnabled) "enabled" else "disabled" + val betaState = if (betaEnabled) "enabled" else "disabled" + return "$prefix $alphaName ($alphaState) DEPENDS ON $betaName ($betaState)" } + /** Used whe posting a notification of unmet dependencies */ + fun shortUnmetString(): String = "$alphaName DEPENDS ON $betaName" } protected infix fun UnreleasedFlag.dependsOn(other: UnreleasedFlag) = @@ -124,7 +134,7 @@ constructor( unmet: List<FlagDependenciesBase.Dependency> ) { val title = "Invalid flag dependencies: ${unmet.size} of ${all.size}" - val details = unmet.joinToString("\n") + val details = unmet.joinToString("\n") { it.shortUnmetString() } Log.e("FlagDependencies", "$title:\n$details") val channel = NotificationChannel("FLAGS", "Flags", NotificationManager.IMPORTANCE_DEFAULT) val notification = diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index b43f54de4b2a..f6db978efadc 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -17,11 +17,11 @@ package com.android.systemui.flags import android.provider.DeviceConfig import com.android.internal.annotations.Keep -import com.android.systemui.res.R import com.android.systemui.flags.FlagsFactory.releasedFlag import com.android.systemui.flags.FlagsFactory.resourceBooleanFlag import com.android.systemui.flags.FlagsFactory.sysPropBooleanFlag import com.android.systemui.flags.FlagsFactory.unreleasedFlag +import com.android.systemui.res.R /** * List of [Flag] objects for use in SystemUI. @@ -483,9 +483,6 @@ object Flags { // TODO(b/264916608): Tracking Bug @JvmField val SCREENSHOT_METADATA = unreleasedFlag("screenshot_metadata") - // TODO(b/266955521): Tracking bug - @JvmField val SCREENSHOT_DETECTION = releasedFlag("screenshot_detection") - // TODO(b/251205791): Tracking Bug @JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips") @@ -607,9 +604,6 @@ object Flags { @JvmField val LOCKSCREEN_WALLPAPER_DREAM_ENABLED = unreleasedFlag("enable_lockscreen_wallpaper_dream") - // TODO(b/283084712): Tracking Bug - @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag("improved_hun_animations") - // TODO(b/283447257): Tracking bug @JvmField val BIGPICTURE_NOTIFICATION_LAZY_LOADING = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 20da00ee3daf..af5d48d9ae07 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -124,7 +124,7 @@ constructor( indicationAreaHandle = KeyguardIndicationAreaBinder.bind( - notificationShadeWindowView, + notificationShadeWindowView.requireViewById(R.id.keyguard_indication_area), keyguardIndicationAreaViewModel, keyguardRootViewModel, indicationController, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt index de15fd6a958f..c98f63717938 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt @@ -22,6 +22,7 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.res.R import javax.inject.Inject @@ -48,7 +49,9 @@ constructor( ) { init { - alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG) + if (!DeviceEntryUdfpsRefactor.isEnabled) { + alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG) + } } private val showIndicatorForPrimaryBouncer: Flow<Boolean> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt deleted file mode 100644 index ebf1beb132f0..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt +++ /dev/null @@ -1,25 +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.keyguard.ui.adapter - -/** - * Temporary adapter class while - * [com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController] is being refactored - * before [com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy] is removed. - * - * TODO (b/278719514): Delete once udfps keyguard view is fully refactored. - */ -interface UdfpsKeyguardViewControllerAdapter diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt index d12d193aa43b..c749818a05e9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt @@ -26,6 +26,7 @@ import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch @ExperimentalCoroutinesApi object AlternateBouncerUdfpsViewBinder { @@ -71,10 +72,12 @@ object AlternateBouncerUdfpsViewBinder { bgView.visibility = View.VISIBLE bgView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.bgViewModel.collect { bgViewModel -> - bgView.alpha = bgViewModel.alpha - bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint) + launch { + viewModel.bgColor.collect { color -> + bgView.imageTintList = ColorStateList.valueOf(color) + } } + launch { viewModel.bgAlpha.collect { alpha -> bgView.alpha = alpha } } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index dcf4284438bf..a02e8ac84a75 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -131,10 +131,10 @@ object DeviceEntryIconViewBinder { bgView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha } } launch { - bgViewModel.viewModel.collect { bgViewModel -> - bgView.alpha = bgViewModel.alpha - bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint) + bgViewModel.color.collect { color -> + bgView.imageTintList = ColorStateList.valueOf(color) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt index 7d290c3c61fd..05fe0b25381d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt @@ -32,8 +32,6 @@ import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.res.R import kotlinx.coroutines.launch -private val TAG = KeyguardClockViewBinder::class.simpleName - object KeyguardClockViewBinder { @JvmStatic fun bind( @@ -74,12 +72,6 @@ object KeyguardClockViewBinder { applyConstraints(clockSection, keyguardRootView, true) } } - launch { - if (!migrateClocksToBlueprint()) return@launch - viewModel.hasCustomWeatherDataDisplay.collect { - applyConstraints(clockSection, keyguardRootView, true) - } - } } } } @@ -132,7 +124,7 @@ object KeyguardClockViewBinder { fun applyConstraints( clockSection: ClockSection, rootView: ConstraintLayout, - animated: Boolean + animated: Boolean, ) { val constraintSet = ConstraintSet().apply { clone(rootView) } clockSection.applyConstraints(constraintSet) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt index 4efd9ef5f21c..4c33d905b785 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt @@ -54,12 +54,11 @@ object KeyguardIndicationAreaBinder { keyguardRootViewModel: KeyguardRootViewModel, indicationController: KeyguardIndicationController, ): DisposableHandle { - val indicationArea: ViewGroup = view.requireViewById(R.id.keyguard_indication_area) - indicationController.setIndicationArea(indicationArea) + indicationController.setIndicationArea(view) - val indicationText: TextView = indicationArea.requireViewById(R.id.keyguard_indication_text) + val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text) val indicationTextBottom: TextView = - indicationArea.requireViewById(R.id.keyguard_indication_text_bottom) + view.requireViewById(R.id.keyguard_indication_text_bottom) view.clipChildren = false view.clipToPadding = false @@ -71,7 +70,7 @@ object KeyguardIndicationAreaBinder { launch { if (keyguardBottomAreaRefactor()) { keyguardRootViewModel.alpha.collect { alpha -> - indicationArea.apply { + view.apply { this.importantForAccessibility = if (alpha == 0f) { View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS @@ -83,7 +82,7 @@ object KeyguardIndicationAreaBinder { } } else { viewModel.alpha.collect { alpha -> - indicationArea.apply { + view.apply { this.importantForAccessibility = if (alpha == 0f) { View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS @@ -98,7 +97,7 @@ object KeyguardIndicationAreaBinder { launch { viewModel.indicationAreaTranslationX.collect { translationX -> - indicationArea.translationX = translationX + view.translationX = translationX } } @@ -113,9 +112,7 @@ object KeyguardIndicationAreaBinder { 0 } } - .collect { paddingPx -> - indicationArea.setPadding(paddingPx, 0, paddingPx, 0) - } + .collect { paddingPx -> view.setPadding(paddingPx, 0, paddingPx, 0) } } launch { @@ -124,7 +121,7 @@ object KeyguardIndicationAreaBinder { .flatMapLatest { defaultBurnInOffsetY -> viewModel.indicationAreaTranslationY(defaultBurnInOffsetY) } - .collect { translationY -> indicationArea.translationY = translationY } + .collect { translationY -> view.translationY = translationY } } launch { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 08e2a8fb6d77..362e7e6d4770 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -29,7 +29,6 @@ import android.view.ViewGroup.OnHierarchyChangeListener import android.view.ViewPropertyAnimator import android.view.WindowInsets import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators import com.android.internal.jank.InteractionJankMonitor @@ -67,6 +66,7 @@ import com.android.systemui.util.ui.value import javax.inject.Provider import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -205,7 +205,6 @@ object KeyguardRootViewBinder { childViews[aodNotificationIconContainerId] ?.setAodNotifIconContainerIsVisible( isVisible, - featureFlags, iconsAppearTranslationPx.value, screenOffAnimationController, ) @@ -359,37 +358,29 @@ object KeyguardRootViewBinder { } } - @JvmStatic - fun bindAodIconVisibility( + suspend fun bindAodNotifIconVisibility( view: View, isVisible: Flow<AnimatedValue<Boolean>>, configuration: ConfigurationState, - featureFlags: FeatureFlagsClassic, screenOffAnimationController: ScreenOffAnimationController, - ): DisposableHandle? { + ) { KeyguardShadeMigrationNssl.assertInLegacyMode() - if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return null - return view.repeatWhenAttached { - lifecycleScope.launch { - val iconAppearTranslationPx = - configuration - .getDimensionPixelSize(R.dimen.shelf_appear_translation) - .stateIn(this) - isVisible.collect { isVisible -> - view.setAodNotifIconContainerIsVisible( - isVisible, - featureFlags, - iconAppearTranslationPx.value, - screenOffAnimationController, - ) - } + if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return + coroutineScope { + val iconAppearTranslationPx = + configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this) + isVisible.collect { isVisible -> + view.setAodNotifIconContainerIsVisible( + isVisible = isVisible, + iconsAppearTranslationPx = iconAppearTranslationPx.value, + screenOffAnimationController = screenOffAnimationController, + ) } } } private fun View.setAodNotifIconContainerIsVisible( isVisible: AnimatedValue<Boolean>, - featureFlags: FeatureFlagsClassic, iconsAppearTranslationPx: Int, screenOffAnimationController: ScreenOffAnimationController, ) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt index 954d2cf6ed8a..e36819b54524 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt @@ -16,13 +16,19 @@ package com.android.systemui.keyguard.ui.binder +import android.transition.TransitionManager +import android.view.View +import androidx.constraintlayout.helper.widget.Layer import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R +import kotlinx.coroutines.launch object KeyguardSmartspaceViewBinder { @JvmStatic @@ -30,15 +36,63 @@ object KeyguardSmartspaceViewBinder { smartspaceSection: SmartspaceSection, keyguardRootView: ConstraintLayout, clockViewModel: KeyguardClockViewModel, + smartspaceViewModel: KeyguardSmartspaceViewModel, ) { keyguardRootView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { - clockViewModel.hasCustomWeatherDataDisplay.collect { - val constraintSet = ConstraintSet().apply { clone(keyguardRootView) } - smartspaceSection.applyConstraints(constraintSet) - constraintSet.applyTo(keyguardRootView) + launch { + clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay + -> + if (hasCustomWeatherDataDisplay) { + removeDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel) + } else { + addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel) + } + clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView) + val constraintSet = ConstraintSet().apply { clone(keyguardRootView) } + smartspaceSection.applyConstraints(constraintSet) + TransitionManager.beginDelayedTransition(keyguardRootView) + constraintSet.applyTo(keyguardRootView) + } } } } } + + private fun addDateWeatherToBurnInLayer( + constraintLayout: ConstraintLayout, + smartspaceViewModel: KeyguardSmartspaceViewModel + ) { + val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer) + burnInLayer.apply { + if ( + smartspaceViewModel.isSmartspaceEnabled && + smartspaceViewModel.isDateWeatherDecoupled + ) { + val dateView = constraintLayout.requireViewById<View>(smartspaceViewModel.dateId) + val weatherView = + constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId) + addView(weatherView) + addView(dateView) + } + } + } + + private fun removeDateWeatherToBurnInLayer( + constraintLayout: ConstraintLayout, + smartspaceViewModel: KeyguardSmartspaceViewModel + ) { + val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer) + burnInLayer.apply { + if ( + smartspaceViewModel.isSmartspaceEnabled && + smartspaceViewModel.isDateWeatherDecoupled + ) { + val dateView = smartspaceViewModel.dateView + val weatherView = smartspaceViewModel.weatherView + removeView(weatherView) + removeView(dateView) + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt deleted file mode 100644 index 52d87d369083..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt +++ /dev/null @@ -1,61 +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.keyguard.ui.binder - -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import com.airbnb.lottie.LottieAnimationView -import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel -import com.android.systemui.lifecycle.repeatWhenAttached -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch - -@ExperimentalCoroutinesApi -object UdfpsAodFingerprintViewBinder { - - /** - * Drives UI for the UDFPS aod fingerprint view. See [UdfpsFingerprintViewBinder] and - * [UdfpsBackgroundViewBinder]. - */ - @JvmStatic - fun bind( - view: LottieAnimationView, - viewModel: UdfpsAodViewModel, - ) { - view.alpha = 0f - view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.burnInOffsets.collect { burnInOffsets -> - view.progress = burnInOffsets.progress - view.translationX = burnInOffsets.x.toFloat() - view.translationY = burnInOffsets.y.toFloat() - } - } - - launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } } - - launch { - viewModel.padding.collect { padding -> - view.setPadding(padding, padding, padding, padding) - } - } - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt deleted file mode 100644 index d4621e6e2356..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt +++ /dev/null @@ -1,87 +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.keyguard.ui.binder - -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import com.airbnb.lottie.LottieAnimationView -import com.airbnb.lottie.LottieProperty -import com.airbnb.lottie.model.KeyPath -import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel -import com.android.systemui.lifecycle.repeatWhenAttached -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch - -@ExperimentalCoroutinesApi -object UdfpsFingerprintViewBinder { - private var udfpsIconColor = 0 - - /** - * Drives UI for the UDFPS fingerprint view when it's NOT on aod. See - * [UdfpsAodFingerprintViewBinder] and [UdfpsBackgroundViewBinder]. - */ - @JvmStatic - fun bind( - view: LottieAnimationView, - viewModel: FingerprintViewModel, - ) { - view.alpha = 0f - view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.transition.collect { - view.alpha = it.alpha - view.scaleX = it.scale - view.scaleY = it.scale - if (udfpsIconColor != (it.color)) { - udfpsIconColor = it.color - view.invalidate() - } - } - } - - launch { - viewModel.burnInOffsets.collect { burnInOffsets -> - view.translationX = burnInOffsets.x.toFloat() - view.translationY = burnInOffsets.y.toFloat() - } - } - - launch { - viewModel.dozeAmount.collect { dozeAmount -> - // Lottie progress represents: aod=0 to lockscreen=1 - view.progress = 1f - dozeAmount - } - } - - launch { - viewModel.padding.collect { padding -> - view.setPadding(padding, padding, padding, padding) - } - } - } - } - - // Add a callback that updates the color to `udfpsIconColor` whenever invalidate is called - view.addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER) { - PorterDuffColorFilter(udfpsIconColor, PorterDuff.Mode.SRC_ATOP) - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt deleted file mode 100644 index aabb3f41e881..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt +++ /dev/null @@ -1,55 +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.biometrics.ui.binder - -import android.view.View -import com.android.systemui.res.R -import com.android.systemui.keyguard.ui.binder.UdfpsAodFingerprintViewBinder -import com.android.systemui.keyguard.ui.binder.UdfpsBackgroundViewBinder -import com.android.systemui.keyguard.ui.binder.UdfpsFingerprintViewBinder -import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel -import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel -import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel -import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel -import kotlinx.coroutines.ExperimentalCoroutinesApi - -@ExperimentalCoroutinesApi -object UdfpsKeyguardInternalViewBinder { - - @JvmStatic - fun bind( - view: View, - viewModel: UdfpsKeyguardInternalViewModel, - aodViewModel: UdfpsAodViewModel, - fingerprintViewModel: FingerprintViewModel, - backgroundViewModel: BackgroundViewModel, - ) { - view.accessibilityDelegate = viewModel.accessibilityDelegate - - // bind child views - UdfpsAodFingerprintViewBinder.bind(view.requireViewById(R.id.udfps_aod_fp), aodViewModel) - UdfpsFingerprintViewBinder.bind( - view.requireViewById(R.id.udfps_lockscreen_fp), - fingerprintViewModel - ) - UdfpsBackgroundViewBinder.bind( - view.requireViewById(R.id.udfps_keyguard_fp_bg), - backgroundViewModel - ) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt deleted file mode 100644 index 475d26f1db54..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt +++ /dev/null @@ -1,111 +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.keyguard.ui.binder - -import android.graphics.RectF -import android.view.View -import android.widget.FrameLayout -import androidx.asynclayoutinflater.view.AsyncLayoutInflater -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.biometrics.UdfpsKeyguardView -import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder -import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel -import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel -import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel -import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel -import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.res.R -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.launch - -@ExperimentalCoroutinesApi -object UdfpsKeyguardViewBinder { - /** - * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view - * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] & - * [UdfpsAodFingerprintViewBinder]. - */ - @JvmStatic - fun bind( - view: UdfpsKeyguardView, - viewModel: UdfpsKeyguardViewModel, - udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel, - aodViewModel: UdfpsAodViewModel, - fingerprintViewModel: FingerprintViewModel, - backgroundViewModel: BackgroundViewModel, - ) { - val layoutInflaterFinishListener = - AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent -> - UdfpsKeyguardInternalViewBinder.bind( - inflatedInternalView, - udfpsKeyguardInternalViewModel, - aodViewModel, - fingerprintViewModel, - backgroundViewModel, - ) - val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams - lp.width = viewModel.sensorBounds.width() - lp.height = viewModel.sensorBounds.height() - val relativeToView = - getBoundsRelativeToView( - inflatedInternalView, - RectF(viewModel.sensorBounds), - ) - lp.setMarginsRelative( - relativeToView.left.toInt(), - relativeToView.top.toInt(), - relativeToView.right.toInt(), - relativeToView.bottom.toInt(), - ) - parent!!.addView(inflatedInternalView, lp) - } - val inflater = AsyncLayoutInflater(view.context) - inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener) - - view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.CREATED) { - launch { - combine(aodViewModel.isVisible, fingerprintViewModel.visible) { - isAodVisible, - isFingerprintVisible -> - isAodVisible || isFingerprintVisible - } - .collect { view.setVisible(it) } - } - } - } - } - - /** - * Converts coordinates of RectF relative to the screen to coordinates relative to this view. - * - * @param bounds RectF based off screen coordinates in current orientation - */ - private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF { - val pos: IntArray = view.locationOnScreen - return RectF( - bounds.left - pos[0], - bounds.top - pos[1], - bounds.right - pos[0], - bounds.bottom - pos[1] - ) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index 1c6a2abdcbe7..bc9671e65f24 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -31,18 +31,21 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopu import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule.Companion.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import java.util.Optional import javax.inject.Inject import javax.inject.Named import kotlin.jvm.optionals.getOrNull +import kotlinx.coroutines.ExperimentalCoroutinesApi /** * Positions elements of the lockscreen to the default position. * * This will be the most common use case for phones in portrait mode. */ +@ExperimentalCoroutinesApi @SysUISingleton @JvmSuppressWildcards class DefaultKeyguardBlueprint @@ -62,6 +65,7 @@ constructor( communalTutorialIndicatorSection: CommunalTutorialIndicatorSection, clockSection: ClockSection, smartspaceSection: SmartspaceSection, + udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection, ) : KeyguardBlueprint { override val id: String = DEFAULT @@ -79,7 +83,8 @@ constructor( aodBurnInSection, communalTutorialIndicatorSection, clockSection, - defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views. + defaultDeviceEntrySection, + udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index bf7068220576..9b404338b9e5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -29,14 +29,17 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotification import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.util.kotlin.getOrNull import java.util.Optional import javax.inject.Inject import javax.inject.Named +import kotlinx.coroutines.ExperimentalCoroutinesApi /** Vertically aligns the shortcuts with the udfps. */ +@ExperimentalCoroutinesApi @SysUISingleton class ShortcutsBesideUdfpsKeyguardBlueprint @Inject @@ -53,6 +56,7 @@ constructor( defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection, aodNotificationIconsSection: AodNotificationIconsSection, aodBurnInSection: AodBurnInSection, + udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection, ) : KeyguardBlueprint { override val id: String = SHORTCUTS_BESIDE_UDFPS @@ -68,7 +72,8 @@ constructor( splitShadeGuidelines, aodNotificationIconsSection, aodBurnInSection, - defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views. + defaultDeviceEntrySection, + udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt index f890ae612ccc..16539db648bc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt @@ -30,6 +30,8 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSec import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule +import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection +import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeClockSection import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection import com.android.systemui.util.kotlin.getOrNull @@ -59,6 +61,8 @@ constructor( aodNotificationIconsSection: AodNotificationIconsSection, aodBurnInSection: AodBurnInSection, communalTutorialIndicatorSection: CommunalTutorialIndicatorSection, + smartspaceSection: SmartspaceSection, + clockSection: SplitShadeClockSection, ) : KeyguardBlueprint { override val id: String = ID @@ -73,8 +77,10 @@ constructor( splitShadeNotificationStackScrollLayoutSection, splitShadeGuidelines, aodNotificationIconsSection, + smartspaceSection, aodBurnInSection, communalTutorialIndicatorSection, + clockSection, defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views. ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt index 8166b454fcff..d89e1e41d1bc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -23,7 +23,6 @@ import androidx.constraintlayout.helper.widget.Layer import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.Flags.migrateClocksToBlueprint -import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel @@ -38,7 +37,6 @@ constructor( private val context: Context, private val clockViewModel: KeyguardClockViewModel, private val smartspaceViewModel: KeyguardSmartspaceViewModel, - private val featureFlags: FeatureFlagsClassic, ) : KeyguardSection() { lateinit var burnInLayer: Layer @@ -59,6 +57,8 @@ constructor( } } if (migrateClocksToBlueprint()) { + // weather and date parts won't be added here, cause their visibility doesn't align + // with others in burnInLayer addSmartspaceViews(constraintLayout) } constraintLayout.addView(burnInLayer) @@ -89,14 +89,6 @@ constructor( val smartspaceView = constraintLayout.requireViewById<View>(smartspaceViewModel.smartspaceViewId) addView(smartspaceView) - if (smartspaceViewModel.isDateWeatherDecoupled) { - val dateView = - constraintLayout.requireViewById<View>(smartspaceViewModel.dateId) - val weatherView = - constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId) - addView(weatherView) - addView(dateView) - } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index 39a0547ded26..b429ab4fac0a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -28,7 +28,6 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.common.ui.ConfigurationState -import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel @@ -49,7 +48,6 @@ class AodNotificationIconsSection constructor( private val context: Context, private val configurationState: ConfigurationState, - private val featureFlags: FeatureFlagsClassic, private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore, @@ -119,14 +117,8 @@ constructor( } constraintSet.apply { if (migrateClocksToBlueprint()) { - connect( - nicId, - TOP, - smartspaceViewModel.smartspaceViewId, - topAlignment, - bottomMargin - ) - setGoneMargin(nicId, topAlignment, bottomMargin) + connect(nicId, TOP, smartspaceViewModel.smartspaceViewId, BOTTOM, bottomMargin) + setGoneMargin(nicId, BOTTOM, bottomMargin) } else { connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index 1df920aab833..656c75c7bfec 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -27,7 +27,7 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT -import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder @@ -50,19 +50,21 @@ internal fun ConstraintSet.setAlpha( alpha: Float, ) = views.forEach { view -> this.setAlpha(view.id, alpha) } -class ClockSection +open class ClockSection @Inject constructor( private val clockInteractor: KeyguardClockInteractor, - private val keyguardClockViewModel: KeyguardClockViewModel, - val smartspaceViewModel: KeyguardSmartspaceViewModel, + protected val keyguardClockViewModel: KeyguardClockViewModel, + private val smartspaceViewModel: KeyguardSmartspaceViewModel, private val context: Context, private val splitShadeStateController: SplitShadeStateController, - private val featureFlags: FeatureFlagsClassic, ) : KeyguardSection() { override fun addViews(constraintLayout: ConstraintLayout) {} override fun bindData(constraintLayout: ConstraintLayout) { + if (!Flags.migrateClocksToBlueprint()) { + return + } KeyguardClockViewBinder.bind( this, constraintLayout, @@ -72,6 +74,9 @@ constructor( } override fun applyConstraints(constraintSet: ConstraintSet) { + if (!Flags.migrateClocksToBlueprint()) { + return + } clockInteractor.clock?.let { clock -> constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet)) } @@ -94,16 +99,6 @@ constructor( } } - var largeClockEndGuideline = PARENT_ID - - // Return if largeClockEndGuideline changes, - // and use it to decide whether to refresh blueprint - fun setClockShouldBeCentered(shouldBeCentered: Boolean): Boolean { - val previousValue = largeClockEndGuideline - largeClockEndGuideline = if (shouldBeCentered) PARENT_ID else R.id.split_shade_guideline - return previousValue != largeClockEndGuideline - } - private fun getTargetClockFace(clock: ClockController): ClockFaceLayout = if (keyguardClockViewModel.useLargeClock) getLargeClockFace(clock) else getSmallClockFace(clock) @@ -113,10 +108,10 @@ constructor( private fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout private fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout - fun applyDefaultConstraints(constraints: ConstraintSet) { + open fun applyDefaultConstraints(constraints: ConstraintSet) { constraints.apply { connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START) - connect(R.id.lockscreen_clock_view_large, END, largeClockEndGuideline, END) + connect(R.id.lockscreen_clock_view_large, END, PARENT_ID, END) connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP) var largeClockTopMargin = context.resources.getDimensionPixelSize(R.dimen.status_bar_height) + diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt index 56f717d7e4ef..66c137f7d75e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt @@ -54,7 +54,7 @@ constructor( if (keyguardBottomAreaRefactor()) { indicationAreaHandle = KeyguardIndicationAreaBinder.bind( - constraintLayout, + constraintLayout.requireViewById(R.id.keyguard_indication_area), keyguardIndicationAreaViewModel, keyguardRootViewModel, indicationController, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt new file mode 100644 index 000000000000..e1a33dea2257 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt @@ -0,0 +1,85 @@ +/* + * 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.keyguard.ui.view.layout.sections + +import android.content.Context +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.Flags +import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder +import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay +import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** Positions the UDFPS accessibility overlay on the bottom half of the keyguard. */ +@ExperimentalCoroutinesApi +class DefaultUdfpsAccessibilityOverlaySection +@Inject +constructor( + private val context: Context, + private val viewModel: UdfpsAccessibilityOverlayViewModel, +) : KeyguardSection() { + private val viewId = R.id.udfps_accessibility_overlay + private var cachedConstraintLayout: ConstraintLayout? = null + + override fun addViews(constraintLayout: ConstraintLayout) { + cachedConstraintLayout = constraintLayout + constraintLayout.addView(UdfpsAccessibilityOverlay(context).apply { id = viewId }) + } + + override fun bindData(constraintLayout: ConstraintLayout) { + UdfpsAccessibilityOverlayBinder.bind( + constraintLayout.findViewById(viewId)!!, + viewModel, + ) + } + + override fun applyConstraints(constraintSet: ConstraintSet) { + constraintSet.apply { + connect(viewId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START) + connect(viewId, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END) + + create(R.id.udfps_accessibility_overlay_top_guideline, ConstraintSet.HORIZONTAL) + setGuidelinePercent(R.id.udfps_accessibility_overlay_top_guideline, .5f) + connect( + viewId, + ConstraintSet.TOP, + R.id.udfps_accessibility_overlay_top_guideline, + ConstraintSet.BOTTOM, + ) + + if (Flags.keyguardBottomAreaRefactor()) { + connect( + viewId, + ConstraintSet.BOTTOM, + R.id.keyguard_indication_area, + ConstraintSet.TOP, + ) + } else { + connect(viewId, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM) + } + } + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + constraintLayout.removeView(viewId) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 368b388062a1..8cd51cd3b1e3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context -import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM @@ -36,7 +35,7 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import javax.inject.Inject -class SmartspaceSection +open class SmartspaceSection @Inject constructor( val keyguardClockViewModel: KeyguardClockViewModel, @@ -45,9 +44,13 @@ constructor( val smartspaceController: LockscreenSmartspaceController, val keyguardUnlockAnimationController: KeyguardUnlockAnimationController, ) : KeyguardSection() { - private var smartspaceView: View? = null - private var weatherView: View? = null - private var dateView: View? = null + var smartspaceView by keyguardSmartspaceViewModel::smartspaceView + var weatherView by keyguardSmartspaceViewModel::weatherView + var dateView by keyguardSmartspaceViewModel::dateView + + val smartspaceViewId = keyguardSmartspaceViewModel.smartspaceViewId + val weatherViewId = keyguardSmartspaceViewModel.weatherId + val dateViewId = keyguardSmartspaceViewModel.dateId override fun addViews(constraintLayout: ConstraintLayout) { if (!migrateClocksToBlueprint()) { @@ -67,10 +70,21 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - KeyguardSmartspaceViewBinder.bind(this, constraintLayout, keyguardClockViewModel) + if (!migrateClocksToBlueprint()) { + return + } + KeyguardSmartspaceViewBinder.bind( + this, + constraintLayout, + keyguardClockViewModel, + keyguardSmartspaceViewModel, + ) } override fun applyConstraints(constraintSet: ConstraintSet) { + if (!migrateClocksToBlueprint()) { + return + } // Generally, weather should be next to dateView // smartspace should be below date & weather views constraintSet.apply { @@ -130,7 +144,20 @@ constructor( } } } - updateVisibility(constraintSet) + } + updateVisibility(constraintSet) + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + if (!migrateClocksToBlueprint()) { + return + } + listOf(smartspaceView, dateView, weatherView).forEach { + it?.let { + if (it.parent == constraintLayout) { + constraintLayout.removeView(it) + } + } } } @@ -158,14 +185,4 @@ constructor( } } } - - override fun removeViews(constraintLayout: ConstraintLayout) { - listOf(smartspaceView, dateView, weatherView).forEach { - it?.let { - if (it.parent == constraintLayout) { - constraintLayout.removeView(it) - } - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt new file mode 100644 index 000000000000..1302bfa6fc31 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt @@ -0,0 +1,58 @@ +/* + * 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.keyguard.ui.view.layout.sections + +import android.content.Context +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel +import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.SplitShadeStateController +import javax.inject.Inject + +class SplitShadeClockSection +@Inject +constructor( + clockInteractor: KeyguardClockInteractor, + keyguardClockViewModel: KeyguardClockViewModel, + smartspaceViewModel: KeyguardSmartspaceViewModel, + context: Context, + splitShadeStateController: SplitShadeStateController, +) : + ClockSection( + clockInteractor, + keyguardClockViewModel, + smartspaceViewModel, + context, + splitShadeStateController + ) { + override fun applyDefaultConstraints(constraints: ConstraintSet) { + super.applyDefaultConstraints(constraints) + val largeClockEndGuideline = + if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID + else R.id.split_shade_guideline + constraints.apply { + connect( + R.id.lockscreen_clock_view_large, + ConstraintSet.END, + largeClockEndGuideline, + ConstraintSet.END + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt index b0b5c81dd11c..0f8e67340cc7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt @@ -23,7 +23,6 @@ import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP -import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel @@ -72,25 +71,9 @@ constructor( return } constraintSet.apply { - val bottomMargin = - context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin) - - if (migrateClocksToBlueprint()) { - connect( - R.id.nssl_placeholder, - TOP, - smartspaceViewModel.smartspaceViewId, - TOP, - bottomMargin - ) - setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin) - } else { - val splitShadeTopMargin = - context.resources.getDimensionPixelSize( - R.dimen.large_screen_shade_header_height - ) - connect(R.id.nssl_placeholder, TOP, PARENT_ID, TOP, splitShadeTopMargin) - } + val splitShadeTopMargin = + context.resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height) + connect(R.id.nssl_placeholder, TOP, PARENT_ID, TOP, splitShadeTopMargin) connect(R.id.nssl_placeholder, START, PARENT_ID, START) connect(R.id.nssl_placeholder, END, PARENT_ID, END) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt index e18893a381f6..f4ae365b2613 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt @@ -43,6 +43,7 @@ constructor( val context: Context, configurationInteractor: ConfigurationInteractor, deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, + deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel, fingerprintPropertyRepository: FingerprintPropertyRepository, ) { private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported @@ -90,26 +91,8 @@ constructor( ) } - private val bgColor: Flow<Int> = - configurationInteractor.onAnyConfigurationChange - .map { - Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurface) - } - .onStart { - emit( - Utils.getColorAttrDefaultColor( - context, - com.android.internal.R.attr.colorSurface - ) - ) - } - val bgViewModel: Flow<DeviceEntryBackgroundViewModel.BackgroundViewModel> = - bgColor.map { color -> - DeviceEntryBackgroundViewModel.BackgroundViewModel( - alpha = 1f, - tint = color, - ) - } + val bgColor: Flow<Int> = deviceEntryBackgroundViewModel.color + val bgAlpha: Flow<Float> = flowOf(1f) data class IconLocation( val left: Int, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt index c45caf0b18fd..be9ae1db79e6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt @@ -19,11 +19,10 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context import com.android.settingslib.Utils -import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart @@ -34,7 +33,7 @@ class DeviceEntryBackgroundViewModel @Inject constructor( val context: Context, - configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor + configurationInteractor: ConfigurationInteractor, lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel, aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, goneToAodTransitionViewModel: GoneToAodTransitionViewModel, @@ -44,8 +43,8 @@ constructor( dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel, ) { - private val color: Flow<Int> = - configurationRepository.onAnyConfigurationChange + val color: Flow<Int> = + configurationInteractor.onAnyConfigurationChange .map { Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurface) } @@ -57,7 +56,7 @@ constructor( ) ) } - private val alpha: Flow<Float> = + val alpha: Flow<Float> = setOf( lockscreenToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, aodToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha, @@ -69,17 +68,4 @@ constructor( alternateBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, ) .merge() - - val viewModel: Flow<BackgroundViewModel> = - combine(color, alpha) { color, alpha -> - BackgroundViewModel( - alpha = alpha, - tint = color, - ) - } - - data class BackgroundViewModel( - val alpha: Float, - val tint: Int, - ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt index 3aeff61c15e7..528a2eebc9cd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt @@ -27,8 +27,8 @@ import com.android.systemui.keyguard.shared.model.SettingsClockSize import com.android.systemui.plugins.clocks.ClockController import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn @@ -79,10 +79,10 @@ constructor( ?: false ) - val clockShouldBeCentered: Flow<Boolean> = + val clockShouldBeCentered: StateFlow<Boolean> = keyguardInteractor.clockShouldBeCentered.stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = true + initialValue = false ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt index 4541458892bb..26e7ee8f561b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context import android.util.Log +import android.view.View import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import javax.inject.Inject @@ -25,7 +26,7 @@ import javax.inject.Inject @SysUISingleton class KeyguardSmartspaceViewModel @Inject -constructor(val context: Context, smartspaceController: LockscreenSmartspaceController) { +constructor(val context: Context, val smartspaceController: LockscreenSmartspaceController) { val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled() val isWeatherEnabled: Boolean = smartspaceController.isWeatherEnabled() val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled() @@ -38,6 +39,10 @@ constructor(val context: Context, smartspaceController: LockscreenSmartspaceCont val weatherId: Int get() = getId("weather_smartspace_view") + var smartspaceView: View? = null + var weatherView: View? = null + var dateView: View? = null + private fun getId(name: String): Int { return context.resources.getIdentifier(name, "id", context.packageName).also { if (it == 0) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt new file mode 100644 index 000000000000..d57e569ca7c8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt @@ -0,0 +1,50 @@ +/* + * 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.keyguard.ui.viewmodel + +import com.android.systemui.biometrics.AuthController +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +@SysUISingleton +class LockscreenContentViewModel +@Inject +constructor( + private val interactor: KeyguardBlueprintInteractor, + private val authController: AuthController, +) { + val isUdfpsVisible: Boolean + get() = authController.isUdfpsSupported + + fun blueprintId(scope: CoroutineScope): StateFlow<String> { + return interactor.blueprint + .map { it.id } + .distinctUntilChanged() + .stateIn( + scope = scope, + started = SharingStarted.WhileSubscribed(), + initialValue = interactor.getCurrentBlueprint().id, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt deleted file mode 100644 index 6e77e13e8513..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt +++ /dev/null @@ -1,47 +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.keyguard.ui.viewmodel - -import android.content.Context -import com.android.systemui.keyguard.domain.interactor.Offsets -import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor -import com.android.systemui.res.R -import javax.inject.Inject -import kotlin.math.roundToInt -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map - -/** View-model for UDFPS AOD view. */ -@ExperimentalCoroutinesApi -class UdfpsAodViewModel -@Inject -constructor( - val interactor: UdfpsKeyguardInteractor, - val context: Context, -) { - val alpha: Flow<Float> = interactor.dozeAmount - val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets - val isVisible: Flow<Boolean> = alpha.map { it != 0f } - - // Padding between the fingerprint icon and its bounding box in pixels. - val padding: Flow<Int> = - interactor.scaleForResolution.map { scale -> - (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) - .roundToInt() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt deleted file mode 100644 index d894a1139eeb..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt +++ /dev/null @@ -1,26 +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.keyguard.ui.viewmodel - -import com.android.systemui.biometrics.UdfpsKeyguardAccessibilityDelegate -import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi - -@ExperimentalCoroutinesApi -class UdfpsKeyguardInternalViewModel -@Inject -constructor(val accessibilityDelegate: UdfpsKeyguardAccessibilityDelegate) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt deleted file mode 100644 index 098b481491de..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.android.systemui.keyguard.ui.viewmodel - -import android.graphics.Rect -import com.android.systemui.biometrics.UdfpsKeyguardView -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.ui.binder.UdfpsKeyguardViewBinder -import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi - -@ExperimentalCoroutinesApi -@SysUISingleton -class UdfpsKeyguardViewModels -@Inject -constructor( - private val viewModel: UdfpsKeyguardViewModel, - private val internalViewModel: UdfpsKeyguardInternalViewModel, - private val aodViewModel: UdfpsAodViewModel, - private val lockscreenFingerprintViewModel: FingerprintViewModel, - private val lockscreenBackgroundViewModel: BackgroundViewModel, -) { - - fun setSensorBounds(sensorBounds: Rect) { - viewModel.sensorBounds = sensorBounds - } - - fun bindViews(view: UdfpsKeyguardView) { - UdfpsKeyguardViewBinder.bind( - view, - viewModel, - internalViewModel, - aodViewModel, - lockscreenFingerprintViewModel, - lockscreenBackgroundViewModel - ) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt deleted file mode 100644 index 642904df21b7..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt +++ /dev/null @@ -1,220 +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.keyguard.ui.viewmodel - -import android.content.Context -import androidx.annotation.ColorInt -import com.android.settingslib.Utils.getColorAttrDefaultColor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.domain.interactor.Offsets -import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.StatusBarState -import com.android.systemui.res.R -import com.android.wm.shell.animation.Interpolators -import javax.inject.Inject -import kotlin.math.roundToInt -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge - -/** View-model for UDFPS lockscreen views. */ -@ExperimentalCoroutinesApi -open class UdfpsLockscreenViewModel( - context: Context, - lockscreenColorResId: Int, - alternateBouncerColorResId: Int, - transitionInteractor: KeyguardTransitionInteractor, - udfpsKeyguardInteractor: UdfpsKeyguardInteractor, - keyguardInteractor: KeyguardInteractor, -) { - private val toLockscreen: Flow<TransitionViewModel> = - transitionInteractor.anyStateToLockscreenTransition.map { - TransitionViewModel( - alpha = - if (it.from == KeyguardState.AOD) { - it.value // animate - } else { - 1f - }, - scale = 1f, - color = getColorAttrDefaultColor(context, lockscreenColorResId), - ) - } - - private val toAlternateBouncer: Flow<TransitionViewModel> = - keyguardInteractor.statusBarState.flatMapLatest { statusBarState -> - transitionInteractor.transitionStepsToState(KeyguardState.ALTERNATE_BOUNCER).map { - TransitionViewModel( - alpha = 1f, - scale = - if (visibleInKeyguardState(it.from, statusBarState)) { - 1f - } else { - Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value) - }, - color = getColorAttrDefaultColor(context, alternateBouncerColorResId), - ) - } - } - - private val fadeOut: Flow<TransitionViewModel> = - keyguardInteractor.statusBarState.flatMapLatest { statusBarState -> - merge( - transitionInteractor.anyStateToGoneTransition, - transitionInteractor.anyStateToAodTransition, - transitionInteractor.anyStateToOccludedTransition, - transitionInteractor.anyStateToPrimaryBouncerTransition, - transitionInteractor.anyStateToDreamingTransition, - ) - .map { - TransitionViewModel( - alpha = - if (visibleInKeyguardState(it.from, statusBarState)) { - 1f - it.value - } else { - 0f - }, - scale = 1f, - color = - if (it.from == KeyguardState.ALTERNATE_BOUNCER) { - getColorAttrDefaultColor(context, alternateBouncerColorResId) - } else { - getColorAttrDefaultColor(context, lockscreenColorResId) - }, - ) - } - } - - private fun visibleInKeyguardState( - state: KeyguardState, - statusBarState: StatusBarState - ): Boolean { - return when (state) { - KeyguardState.OFF, - KeyguardState.DOZING, - KeyguardState.DREAMING, - KeyguardState.DREAMING_LOCKSCREEN_HOSTED, - KeyguardState.AOD, - KeyguardState.PRIMARY_BOUNCER, - KeyguardState.GONE, - KeyguardState.OCCLUDED -> false - KeyguardState.LOCKSCREEN -> statusBarState == StatusBarState.KEYGUARD - KeyguardState.ALTERNATE_BOUNCER -> true - } - } - - private val keyguardStateTransition = - merge( - toAlternateBouncer, - toLockscreen, - fadeOut, - ) - - private val dialogHideAffordancesAlphaMultiplier: Flow<Float> = - udfpsKeyguardInteractor.dialogHideAffordancesRequest.map { hideAffordances -> - if (hideAffordances) { - 0f - } else { - 1f - } - } - - private val alphaMultiplier: Flow<Float> = - combine( - transitionInteractor.startedKeyguardState, - dialogHideAffordancesAlphaMultiplier, - udfpsKeyguardInteractor.shadeExpansion, - udfpsKeyguardInteractor.qsProgress, - ) { startedKeyguardState, dialogHideAffordancesAlphaMultiplier, shadeExpansion, qsProgress - -> - if (startedKeyguardState == KeyguardState.ALTERNATE_BOUNCER) { - 1f - } else { - dialogHideAffordancesAlphaMultiplier * (1f - shadeExpansion) * (1f - qsProgress) - } - } - - val transition: Flow<TransitionViewModel> = - combine( - alphaMultiplier, - keyguardStateTransition, - ) { alphaMultiplier, keyguardStateTransition -> - TransitionViewModel( - alpha = keyguardStateTransition.alpha * alphaMultiplier, - scale = keyguardStateTransition.scale, - color = keyguardStateTransition.color, - ) - } - val visible: Flow<Boolean> = transition.map { it.alpha != 0f } -} - -@ExperimentalCoroutinesApi -class FingerprintViewModel -@Inject -constructor( - val context: Context, - transitionInteractor: KeyguardTransitionInteractor, - interactor: UdfpsKeyguardInteractor, - keyguardInteractor: KeyguardInteractor, -) : - UdfpsLockscreenViewModel( - context, - android.R.attr.textColorPrimary, - com.android.internal.R.attr.materialColorOnPrimaryFixed, - transitionInteractor, - interactor, - keyguardInteractor, - ) { - val dozeAmount: Flow<Float> = interactor.dozeAmount - val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets - - // Padding between the fingerprint icon and its bounding box in pixels. - val padding: Flow<Int> = - interactor.scaleForResolution.map { scale -> - (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) - .roundToInt() - } -} - -@ExperimentalCoroutinesApi -class BackgroundViewModel -@Inject -constructor( - val context: Context, - transitionInteractor: KeyguardTransitionInteractor, - interactor: UdfpsKeyguardInteractor, - keyguardInteractor: KeyguardInteractor, -) : - UdfpsLockscreenViewModel( - context, - com.android.internal.R.attr.colorSurface, - com.android.internal.R.attr.materialColorPrimaryFixed, - transitionInteractor, - interactor, - keyguardInteractor, - ) - -data class TransitionViewModel( - val alpha: Float, - val scale: Float, - @ColorInt val color: Int, -) diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 0e7e69b97d8a..270bfbe4274d 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -263,11 +263,7 @@ constructor( * If the shortcut entry `android:enabled` is set to `true`, the shortcut will be visible in the * Widget Picker to all users. */ - // TODO(b/316332684) - @Suppress("UNREACHABLE_CODE") fun setNoteTaskShortcutEnabled(value: Boolean, user: UserHandle) { - return // shortcut should not be enabled until additional features are implemented. - if (!userManager.isUserUnlocked(user)) { debugLog { "setNoteTaskShortcutEnabled call but user locked: user=$user" } return diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java index 1ab64b76b0dc..ba3357c8b591 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java @@ -17,12 +17,10 @@ package com.android.systemui.qs; import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; -import android.os.Build; import android.provider.Settings; -import com.android.systemui.res.R; import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.util.leak.GarbageMonitor; +import com.android.systemui.res.R; import java.util.ArrayList; import java.util.Arrays; @@ -44,10 +42,6 @@ public interface QSHost { final String defaultTileList = res.getString(R.string.quick_settings_tiles_default); tiles.addAll(Arrays.asList(defaultTileList.split(","))); - if (Build.IS_DEBUGGABLE - && GarbageMonitor.ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) { - tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC); - } return tiles; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index 2af7ae0614ac..47b062430ca5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -23,7 +23,6 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; -import android.os.Build; import android.provider.Settings; import android.service.quicksettings.Tile; import android.service.quicksettings.TileService; @@ -33,7 +32,6 @@ import android.widget.Button; import androidx.annotation.Nullable; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.qs.QSTile; @@ -42,8 +40,8 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; -import com.android.systemui.util.leak.GarbageMonitor; import java.util.ArrayList; import java.util.Arrays; @@ -114,9 +112,6 @@ public class TileQueryHelper { possibleTiles.add(spec); } } - if (Build.IS_DEBUGGABLE && !current.contains(GarbageMonitor.MemoryTile.TILE_SPEC)) { - possibleTiles.add(GarbageMonitor.MemoryTile.TILE_SPEC); - } final ArrayList<QSTile> tilesToAdd = new ArrayList<>(); possibleTiles.remove("cell"); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 17e6375967fc..bdcbac09a254 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -14,7 +14,6 @@ package com.android.systemui.qs.tileimpl; -import android.os.Build; import android.util.Log; import androidx.annotation.Nullable; @@ -25,15 +24,14 @@ import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.external.CustomTile; -import com.android.systemui.util.leak.GarbageMonitor; - -import dagger.Lazy; import java.util.Map; import javax.inject.Inject; import javax.inject.Provider; +import dagger.Lazy; + /** * A factory that creates Quick Settings tiles based on a tileSpec * @@ -79,9 +77,7 @@ public class QSFactoryImpl implements QSFactory { @Nullable protected QSTileImpl createTileInternal(String tileSpec) { // Stock tiles. - if (mTileMap.containsKey(tileSpec) - // We should not return a Garbage Monitory Tile if the build is not Debuggable - && (!tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC) || Build.IS_DEBUGGABLE)) { + if (mTileMap.containsKey(tileSpec)) { return mTileMap.get(tileSpec).get(); } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 47518bbee57a..5abb4dde856b 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabl import com.android.systemui.util.asIndenting import com.android.systemui.util.printSection import com.android.systemui.util.println +import dagger.Lazy import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -80,8 +81,8 @@ constructor( private val sceneLogger: SceneLogger, @FalsingCollectorActual private val falsingCollector: FalsingCollector, private val powerInteractor: PowerInteractor, - private val simBouncerInteractor: SimBouncerInteractor, - private val authenticationInteractor: AuthenticationInteractor, + private val simBouncerInteractor: Lazy<SimBouncerInteractor>, + private val authenticationInteractor: Lazy<AuthenticationInteractor>, ) : CoreStartable { override fun start() { @@ -152,7 +153,7 @@ constructor( } } applicationScope.launch { - simBouncerInteractor.isAnySimSecure.collect { isAnySimLocked -> + simBouncerInteractor.get().isAnySimSecure.collect { isAnySimLocked -> val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value val isUnlocked = deviceEntryInteractor.isUnlocked.value @@ -166,15 +167,17 @@ constructor( isUnlocked && canSwipeToEnter == false -> { switchToScene( targetSceneKey = SceneKey.Gone, - loggingReason = "All SIM cards unlocked and device already" + - " unlocked and lockscreen doesn't require a swipe to dismiss." + loggingReason = + "All SIM cards unlocked and device already" + + " unlocked and lockscreen doesn't require a swipe to dismiss." ) } else -> { switchToScene( targetSceneKey = SceneKey.Lockscreen, - loggingReason = "All SIM cards unlocked and device still locked" + - " or lockscreen still requires a swipe to dismiss." + loggingReason = + "All SIM cards unlocked and device still locked" + + " or lockscreen still requires a swipe to dismiss." ) } } @@ -262,7 +265,7 @@ constructor( " to swipe up on lockscreen to enter.", ) } else if ( - authenticationInteractor.getAuthenticationMethod() == + authenticationInteractor.get().getAuthenticationMethod() == AuthenticationMethodModel.Sim ) { switchToScene( diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt index 5cbea90580a1..7130fa1a1bd3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt @@ -11,8 +11,6 @@ import android.view.ViewTreeObserver import android.view.animation.AccelerateDecelerateInterpolator import androidx.constraintlayout.widget.Guideline import com.android.systemui.res.R -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import javax.inject.Inject /** @@ -23,7 +21,6 @@ class MessageContainerController constructor( private val workProfileMessageController: WorkProfileMessageController, private val screenshotDetectionController: ScreenshotDetectionController, - private val featureFlags: FeatureFlags, ) { private lateinit var container: ViewGroup private lateinit var guideline: Guideline @@ -63,10 +60,8 @@ constructor( fun onScreenshotTaken(screenshot: ScreenshotData) { val workProfileData = workProfileMessageController.onScreenshotTaken(screenshot.userHandle) - var notifiedApps: List<CharSequence> = listOf() - if (featureFlags.isEnabled(Flags.SCREENSHOT_DETECTION)) { - notifiedApps = screenshotDetectionController.maybeNotifyOfScreenshot(screenshot) - } + var notifiedApps: List<CharSequence> = + screenshotDetectionController.maybeNotifyOfScreenshot(screenshot) // If work profile first run needs to show, bias towards that, otherwise show screenshot // detection notification if needed. diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 878e6faf32e7..17eb3c83fefe 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1720,9 +1720,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } // To prevent the weather clock from overlapping with the notification shelf on AOD, we use // the small clock here - if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf() - && hasVisibleNotifications() && isOnAod()) { - return SMALL; + // With migrateClocksToBlueprint, weather clock will have behaviors similar to other clocks + if (!migrateClocksToBlueprint()) { + if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf() + && hasVisibleNotifications() && isOnAod()) { + return SMALL; + } } return LARGE; } @@ -1742,8 +1745,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } else { layout = mNotificationContainerParent; } + if (migrateClocksToBlueprint()) { - mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered); + mKeyguardInteractor.setClockShouldBeCentered(mSplitShadeEnabled && shouldBeCentered); } else { mKeyguardStatusViewController.updateAlignment( layout, mSplitShadeEnabled, shouldBeCentered, animate); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt index 3e9c6fbb2ec4..3b48b3922dc5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt @@ -3,10 +3,8 @@ package com.android.systemui.statusbar.notification import android.util.FloatProperty import android.view.View import androidx.annotation.FloatRange -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.flags.RefactorFlag import com.android.systemui.res.R +import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation import com.android.systemui.statusbar.notification.stack.AnimationProperties import com.android.systemui.statusbar.notification.stack.StackStateAnimator import kotlin.math.abs @@ -42,13 +40,13 @@ interface Roundable { /** Current top corner in pixel, based on [topRoundness] and [maxRadius] */ val topCornerRadius: Float get() = - if (roundableState.newHeadsUpAnim.isEnabled) roundableState.topCornerRadius + if (NotificationsImprovedHunAnimation.isEnabled) roundableState.topCornerRadius else topRoundness * maxRadius /** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */ val bottomCornerRadius: Float get() = - if (roundableState.newHeadsUpAnim.isEnabled) roundableState.bottomCornerRadius + if (NotificationsImprovedHunAnimation.isEnabled) roundableState.bottomCornerRadius else bottomRoundness * maxRadius /** Get and update the current radii */ @@ -318,13 +316,10 @@ constructor( internal val targetView: View, private val roundable: Roundable, maxRadius: Float, - featureFlags: FeatureFlags? = null ) { internal var maxRadius = maxRadius private set - internal val newHeadsUpAnim = RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS, featureFlags) - /** Animatable for top roundness */ private val topAnimatable = topAnimatable(roundable) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt index 30e2f0e0a57f..921556854440 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt @@ -21,6 +21,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.wm.shell.bubbles.Bubbles import java.util.Optional @@ -37,10 +38,12 @@ class NotificationIconsInteractor constructor( private val activeNotificationsInteractor: ActiveNotificationsInteractor, private val bubbles: Optional<Bubbles>, + private val headsUpNotificationIconInteractor: HeadsUpNotificationIconInteractor, private val keyguardViewStateRepository: NotificationsKeyguardViewStateRepository, ) { /** Returns a subset of all active notifications based on the supplied filtration parameters. */ fun filteredNotifSet( + forceShowHeadsUp: Boolean = false, showAmbient: Boolean = true, showLowPriority: Boolean = true, showDismissed: Boolean = true, @@ -49,18 +52,21 @@ constructor( ): Flow<Set<ActiveNotificationModel>> { return combine( activeNotificationsInteractor.topLevelRepresentativeNotifications, + headsUpNotificationIconInteractor.isolatedNotification, keyguardViewStateRepository.areNotificationsFullyHidden, - ) { notifications, notifsFullyHidden -> + ) { notifications, isolatedNotifKey, notifsFullyHidden -> notifications .asSequence() .filter { model: ActiveNotificationModel -> shouldShowNotificationIcon( model = model, + forceShowHeadsUp = forceShowHeadsUp, showAmbient = showAmbient, showLowPriority = showLowPriority, showDismissed = showDismissed, showRepliedMessages = showRepliedMessages, showPulsing = showPulsing, + isolatedNotifKey = isolatedNotifKey, notifsFullyHidden = notifsFullyHidden, ) } @@ -70,14 +76,17 @@ constructor( private fun shouldShowNotificationIcon( model: ActiveNotificationModel, + forceShowHeadsUp: Boolean, showAmbient: Boolean, showLowPriority: Boolean, showDismissed: Boolean, showRepliedMessages: Boolean, showPulsing: Boolean, + isolatedNotifKey: String?, notifsFullyHidden: Boolean, ): Boolean { return when { + forceShowHeadsUp && model.key == isolatedNotifKey -> true !showAmbient && model.isAmbient -> false !showLowPriority && model.isSilent -> false !showDismissed && model.isRowDismissed -> false @@ -118,6 +127,7 @@ constructor( val statusBarNotifs: Flow<Set<ActiveNotificationModel>> = settingsRepository.showSilentStatusIcons.flatMapLatest { showSilentIcons -> iconsInteractor.filteredNotifSet( + forceShowHeadsUp = true, showAmbient = false, showLowPriority = showSilentIcons, showDismissed = false, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt new file mode 100644 index 000000000000..d7c29f19fe57 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt @@ -0,0 +1,76 @@ +/* + * 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.icon.ui.viewbinder + +import androidx.lifecycle.lifecycleScope +import com.android.systemui.common.ui.ConfigurationState +import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.notification.collection.NotifCollection +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel +import com.android.systemui.statusbar.phone.NotificationIconContainer +import com.android.systemui.statusbar.phone.ScreenOffAnimationController +import com.android.systemui.statusbar.ui.SystemBarUtilsState +import javax.inject.Inject +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.launch + +/** Binds a [NotificationIconContainer] to a [NotificationIconContainerAlwaysOnDisplayViewModel]. */ +class NotificationIconContainerAlwaysOnDisplayViewBinder +@Inject +constructor( + private val viewModel: NotificationIconContainerAlwaysOnDisplayViewModel, + private val keyguardRootViewModel: KeyguardRootViewModel, + private val configuration: ConfigurationState, + private val failureTracker: StatusBarIconViewBindingFailureTracker, + private val screenOffAnimationController: ScreenOffAnimationController, + private val systemBarUtilsState: SystemBarUtilsState, + private val viewStore: AlwaysOnDisplayNotificationIconViewStore, +) { + fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle { + return view.repeatWhenAttached { + lifecycleScope.launch { + launch { + NotificationIconContainerViewBinder.bind( + view = view, + viewModel = viewModel, + configuration = configuration, + systemBarUtilsState = systemBarUtilsState, + failureTracker = failureTracker, + viewStore = viewStore, + ) + } + launch { + KeyguardRootViewBinder.bindAodNotifIconVisibility( + view = view, + isVisible = keyguardRootViewModel.isNotifIconContainerVisible, + configuration = configuration, + screenOffAnimationController = screenOffAnimationController, + ) + } + } + } + } +} + +/** [IconViewStore] for the always-on display. */ +class AlwaysOnDisplayNotificationIconViewStore +@Inject +constructor(notifCollection: NotifCollection) : + IconViewStore by (notifCollection.iconViewStoreBy { it.aodIcon }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt new file mode 100644 index 000000000000..783488af3a47 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt @@ -0,0 +1,51 @@ +/* + * 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.icon.ui.viewbinder + +import com.android.systemui.common.ui.ConfigurationState +import com.android.systemui.statusbar.notification.collection.NotifCollection +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.bindIcons +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel +import com.android.systemui.statusbar.phone.NotificationIconContainer +import com.android.systemui.statusbar.ui.SystemBarUtilsState +import javax.inject.Inject + +/** Binds a [NotificationIconContainer] to a [NotificationIconContainerShelfViewModel]. */ +class NotificationIconContainerShelfViewBinder +@Inject +constructor( + private val viewModel: NotificationIconContainerShelfViewModel, + private val configuration: ConfigurationState, + private val systemBarUtilsState: SystemBarUtilsState, + private val failureTracker: StatusBarIconViewBindingFailureTracker, + private val viewStore: ShelfNotificationIconViewStore, +) { + suspend fun bind(view: NotificationIconContainer) { + viewModel.icons.bindIcons( + view, + configuration, + systemBarUtilsState, + notifyBindingFailures = { failureTracker.shelfFailures = it }, + viewStore, + ) + } +} + +/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */ +class ShelfNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) : + IconViewStore by (notifCollection.iconViewStoreBy { it.shelfIcon }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt new file mode 100644 index 000000000000..8e089b1f11fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt @@ -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.statusbar.notification.icon.ui.viewbinder + +import androidx.lifecycle.lifecycleScope +import com.android.systemui.common.ui.ConfigurationState +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.notification.collection.NotifCollection +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel +import com.android.systemui.statusbar.phone.NotificationIconContainer +import com.android.systemui.statusbar.ui.SystemBarUtilsState +import javax.inject.Inject +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.launch + +/** Binds a [NotificationIconContainer] to a [NotificationIconContainerStatusBarViewModel]. */ +class NotificationIconContainerStatusBarViewBinder +@Inject +constructor( + private val viewModel: NotificationIconContainerStatusBarViewModel, + private val configuration: ConfigurationState, + private val systemBarUtilsState: SystemBarUtilsState, + private val failureTracker: StatusBarIconViewBindingFailureTracker, + private val viewStore: StatusBarNotificationIconViewStore, +) { + fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle { + return view.repeatWhenAttached { + lifecycleScope.launch { + NotificationIconContainerViewBinder.bind( + view = view, + viewModel = viewModel, + configuration = configuration, + systemBarUtilsState = systemBarUtilsState, + failureTracker = failureTracker, + viewStore = viewStore, + ) + } + } + } +} + +/** [IconViewStore] for the status bar. */ +class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) : + IconViewStore by (notifCollection.iconViewStoreBy { it.statusBarIcon }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index e1e30e1d74f0..8fe00225463b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -35,7 +35,6 @@ import com.android.systemui.statusbar.notification.icon.IconPack import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData.LimitType @@ -45,7 +44,6 @@ import com.android.systemui.util.kotlin.mapValuesNotNullTo import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.stopAnimating import com.android.systemui.util.ui.value -import javax.inject.Inject import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope @@ -56,42 +54,6 @@ import kotlinx.coroutines.launch /** Binds a view-model to a [NotificationIconContainer]. */ object NotificationIconContainerViewBinder { - @JvmStatic - fun bindWhileAttached( - view: NotificationIconContainer, - viewModel: NotificationIconContainerShelfViewModel, - configuration: ConfigurationState, - systemBarUtilsState: SystemBarUtilsState, - failureTracker: StatusBarIconViewBindingFailureTracker, - viewStore: IconViewStore, - ): DisposableHandle { - return view.repeatWhenAttached { - lifecycleScope.launch { - viewModel.icons.bindIcons( - view, - configuration, - systemBarUtilsState, - notifyBindingFailures = { failureTracker.shelfFailures = it }, - viewStore, - ) - } - } - } - - @JvmStatic - fun bindWhileAttached( - view: NotificationIconContainer, - viewModel: NotificationIconContainerStatusBarViewModel, - configuration: ConfigurationState, - systemBarUtilsState: SystemBarUtilsState, - failureTracker: StatusBarIconViewBindingFailureTracker, - viewStore: IconViewStore, - ): DisposableHandle = - view.repeatWhenAttached { - lifecycleScope.launch { - bind(view, viewModel, configuration, systemBarUtilsState, failureTracker, viewStore) - } - } suspend fun bind( view: NotificationIconContainer, @@ -215,7 +177,7 @@ object NotificationIconContainerViewBinder { * given `iconKey`. The parent [Job] of this coroutine will be cancelled automatically when the * view is to be unbound. */ - private suspend fun Flow<NotificationIconsViewData>.bindIcons( + suspend fun Flow<NotificationIconsViewData>.bindIcons( view: NotificationIconContainer, configuration: ConfigurationState, systemBarUtilsState: SystemBarUtilsState, @@ -377,24 +339,14 @@ object NotificationIconContainerViewBinder { } @ColorInt private const val DEFAULT_AOD_ICON_COLOR = Color.WHITE - private const val TAG = "NotifIconContainerViewBinder" + private const val TAG = "NotifIconContainerViewBinder" } -/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */ -class ShelfNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) : - IconViewStore by (notifCollection.iconViewStoreBy { it.shelfIcon }) - -/** [IconViewStore] for the always-on display. */ -class AlwaysOnDisplayNotificationIconViewStore -@Inject -constructor(notifCollection: NotifCollection) : - IconViewStore by (notifCollection.iconViewStoreBy { it.aodIcon }) - -/** [IconViewStore] for the status bar. */ -class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) : - IconViewStore by (notifCollection.iconViewStoreBy { it.statusBarIcon }) - -private fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) = +/** + * Convenience builder for [IconViewStore] that uses [block] to extract the relevant + * [StatusBarIconView] from an [IconPack] stored inside of the [NotifCollection]. + */ +fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) = IconViewStore { key -> getEntry(key)?.icons?.let(block) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt index 6e5ac470f1b6..d00cd1fcfed9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt @@ -96,8 +96,8 @@ constructor( iconsViewData.visibleIcons.firstOrNull { it.notifKey == isolatedNotif } } } - .pairwise(initialValue = null) .distinctUntilChanged() + .pairwise(initialValue = null) .sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion -> val animate = when { 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 4fe05ec9990c..fca527f5fc4d 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 @@ -31,7 +31,6 @@ import android.view.Choreographer; import android.view.MotionEvent; import android.view.View; import android.view.animation.Interpolator; -import android.view.animation.PathInterpolator; import com.android.app.animation.Interpolators; import com.android.internal.jank.InteractionJankMonitor; @@ -44,6 +43,7 @@ import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; +import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.util.DumpUtilsKt; @@ -67,7 +67,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView * The content of the view should start showing at animation progress value of * #ALPHA_APPEAR_START_FRACTION. */ - private static final float ALPHA_APPEAR_START_FRACTION = .4f; + + private static final float ALPHA_APPEAR_START_FRACTION = .7f; /** * The content should show fully with progress at #ALPHA_APPEAR_END_FRACTION * The start of the animation is at #ALPHA_APPEAR_START_FRACTION @@ -86,9 +87,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView */ private boolean mActivated; - private final Interpolator mSlowOutFastInInterpolator; private Interpolator mCurrentAppearInterpolator; - NotificationBackgroundView mBackgroundNormal; private float mAnimationTranslationY; private boolean mDrawingAppearAnimation; @@ -116,7 +115,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); - mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f); setClipChildren(false); setClipToPadding(false); updateColors(); @@ -400,12 +398,16 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN; targetValue = 1.0f; } else { - mCurrentAppearInterpolator = mSlowOutFastInInterpolator; + mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE; targetValue = 0.0f; } mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction, targetValue); - mAppearAnimator.setInterpolator(Interpolators.LINEAR); + if (NotificationsImprovedHunAnimation.isEnabled()) { + mAppearAnimator.setInterpolator(mCurrentAppearInterpolator); + } else { + mAppearAnimator.setInterpolator(Interpolators.LINEAR); + } mAppearAnimator.setDuration( (long) (duration * Math.abs(mAppearAnimationFraction - targetValue))); mAppearAnimator.addUpdateListener(animation -> { @@ -502,8 +504,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } private void updateAppearRect() { - float interpolatedFraction = mCurrentAppearInterpolator.getInterpolation( - mAppearAnimationFraction); + float interpolatedFraction = + NotificationsImprovedHunAnimation.isEnabled() ? mAppearAnimationFraction + : mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction); mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY; final int actualHeight = getActualHeight(); float bottom = actualHeight * interpolatedFraction; @@ -524,6 +527,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } private float getInterpolatedAppearAnimationFraction() { + if (mAppearAnimationFraction >= 0) { return mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction); } @@ -569,7 +573,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public float getTopCornerRadius() { - if (mImprovedHunAnimation.isEnabled()) { + if (NotificationsImprovedHunAnimation.isEnabled()) { return super.getTopCornerRadius(); } @@ -579,7 +583,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public float getBottomCornerRadius() { - if (mImprovedHunAnimation.isEnabled()) { + if (NotificationsImprovedHunAnimation.isEnabled()) { return super.getBottomCornerRadius(); } 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 5872840913f4..31ca106d2bc9 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 @@ -513,15 +513,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private void setImageViewAnimationRunning(ImageView imageView, boolean running) { if (imageView != null) { Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AnimationDrawable) { - AnimationDrawable animationDrawable = (AnimationDrawable) drawable; + if (drawable instanceof AnimationDrawable animationDrawable) { if (running) { animationDrawable.start(); } else { animationDrawable.stop(); } - } else if (drawable instanceof AnimatedVectorDrawable) { - AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable; + } else if (drawable instanceof AnimatedVectorDrawable animationDrawable) { if (running) { animationDrawable.start(); } else { @@ -3439,8 +3437,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected boolean childNeedsClipping(View child) { - if (child instanceof NotificationContentView) { - NotificationContentView contentView = (NotificationContentView) child; + if (child instanceof NotificationContentView contentView) { if (isClippingNeeded()) { return true; } else if (hasRoundedCorner() @@ -3522,8 +3519,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void applyToView(View view) { - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (view instanceof ExpandableNotificationRow row) { if (row.isExpandAnimationRunning()) { return; } @@ -3543,8 +3539,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected void onYTranslationAnimationFinished(View view) { super.onYTranslationAnimationFinished(view); - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (view instanceof ExpandableNotificationRow row) { if (row.isHeadsUpAnimatingAway()) { row.setHeadsUpAnimatingAway(false); } @@ -3553,8 +3548,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void animateTo(View child, AnimationProperties properties) { - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (child instanceof ExpandableNotificationRow row) { if (row.isExpandAnimationRunning()) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index 2a3e69b7f4d4..aefd34817fdc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -28,10 +28,9 @@ import android.util.IndentingPrintWriter; import android.view.View; import android.view.ViewOutlineProvider; -import com.android.systemui.flags.Flags; -import com.android.systemui.flags.RefactorFlag; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.RoundableState; +import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; import com.android.systemui.util.DumpUtilsKt; @@ -49,8 +48,6 @@ public abstract class ExpandableOutlineView extends ExpandableView { private float mOutlineAlpha = -1f; private boolean mAlwaysRoundBothCorners; private Path mTmpPath = new Path(); - protected final RefactorFlag mImprovedHunAnimation = - RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS); /** * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when @@ -126,7 +123,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { return EMPTY_PATH; } float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius(); - if (!mImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) { + if (!NotificationsImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) { float overShoot = topRadius + bottomRadius - height; float currentTopRoundness = getTopRoundness(); float currentBottomRoundness = getBottomRoundness(); 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 49674d603509..c4d266ed2f17 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 @@ -676,8 +676,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro mViewState.headsUpIsVisible = false; // handling reset for child notifications - if (this instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) this; + if (this instanceof ExpandableNotificationRow row) { List<ExpandableNotificationRow> children = row.getAttachedChildren(); if (row.isSummaryWithChildren() && children != null) { for (ExpandableNotificationRow childRow : children) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt new file mode 100644 index 000000000000..16d35fe9fa68 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt @@ -0,0 +1,53 @@ +/* + * 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.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the notifications improved hun animation flag state. */ +@Suppress("NOTHING_TO_INLINE") +object NotificationsImprovedHunAnimation { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.notificationsImprovedHunAnimation() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt index 699e1406bc18..5ab4d4eae929 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt @@ -16,60 +16,38 @@ package com.android.systemui.statusbar.notification.shelf.ui.viewbinder -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.common.ui.ConfigurationState -import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.NotificationShelf -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel import com.android.systemui.statusbar.phone.NotificationIconAreaController -import com.android.systemui.statusbar.ui.SystemBarUtilsState import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch /** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */ object NotificationShelfViewBinder { - fun bind( + suspend fun bind( shelf: NotificationShelf, viewModel: NotificationShelfViewModel, - configuration: ConfigurationState, - systemBarUtilsState: SystemBarUtilsState, falsingManager: FalsingManager, - iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker, + nicBinder: NotificationIconContainerShelfViewBinder, notificationIconAreaController: NotificationIconAreaController, - shelfIconViewStore: ShelfNotificationIconViewStore, - ) { + ): Unit = coroutineScope { ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager) shelf.apply { if (NotificationIconContainerRefactor.isEnabled) { - NotificationIconContainerViewBinder.bindWhileAttached( - shelfIcons, - viewModel.icons, - configuration, - systemBarUtilsState, - iconViewBindingFailureTracker, - shelfIconViewStore, - ) + launch { nicBinder.bind(shelfIcons) } } else { notificationIconAreaController.setShelfIcons(shelfIcons) } - repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.canModifyColorOfNotifications.collect( - ::setCanModifyColorOfNotifications - ) - } - launch { viewModel.isClickable.collect(::setCanInteract) } - registerViewListenersWhileAttached(shelf, viewModel) - } + launch { + viewModel.canModifyColorOfNotifications.collect(::setCanModifyColorOfNotifications) } + launch { viewModel.isClickable.collect(::setCanInteract) } + registerViewListenersWhileAttached(shelf, viewModel) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt index 64b5b62c4331..5ca8b53d0704 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.shelf.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.NotificationShelf -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor import javax.inject.Inject @@ -32,7 +31,6 @@ class NotificationShelfViewModel constructor( private val interactor: NotificationShelfInteractor, activatableViewModel: ActivatableNotificationViewModel, - val icons: NotificationIconContainerShelfViewModel, ) : ActivatableNotificationViewModel by activatableViewModel { /** Is the shelf allowed to be clickable when it has content? */ val isClickable: Flow<Boolean> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 0236fc265add..45b9c269b61c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -869,8 +869,7 @@ public class NotificationChildrenContainer extends ViewGroup Path clipPath = mChildClipPath; if (clipPath != null) { final float translation; - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) child; + if (child instanceof ExpandableNotificationRow notificationRow) { translation = notificationRow.getTranslation(); } else { translation = child.getTranslationX(); 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 85e63e58bc7a..0f640c9c2608 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 @@ -112,6 +112,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; +import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; @@ -992,8 +993,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child.getVisibility() != View.GONE - && child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; + && child instanceof ExpandableNotificationRow row) { if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0 && row.getProvider().shouldShowGutsOnSnapOpen()) { top = Math.min(top, row.getTranslationY()); @@ -1128,10 +1128,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable for (int i = 0; i < n; i++) { View view = getChildAt(i); if (view.getVisibility() == View.GONE - || !(view instanceof ExpandableNotificationRow)) { + || !(view instanceof ExpandableNotificationRow row)) { continue; } - ExpandableNotificationRow row = (ExpandableNotificationRow) view; currentIndex++; boolean beforeSpeedBump; if (mHighPriorityBeforeSpeedBump) { @@ -1768,16 +1767,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } public static boolean isPinnedHeadsUp(View v) { - if (v instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (v instanceof ExpandableNotificationRow row) { return row.isHeadsUp() && row.isPinned(); } return false; } private boolean isHeadsUp(View v) { - if (v instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (v instanceof ExpandableNotificationRow row) { return row.isHeadsUp(); } return false; @@ -1819,8 +1816,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if ((bottom - top >= mMinInteractionHeight || !requireMinHeight) && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { - if (slidingChild instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; + if (slidingChild instanceof ExpandableNotificationRow row) { NotificationEntry entry = row.getEntry(); if (!mIsExpanded && row.isHeadsUp() && row.isPinned() && mTopHeadsUpEntry.getRow() != row @@ -2363,8 +2359,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable float rowTranslation = child.getTranslationY(); if (rowTranslation >= translationY) { return child; - } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; + } else if (!ignoreChildren && child instanceof ExpandableNotificationRow row) { if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { List<ExpandableNotificationRow> notificationChildren = row.getAttachedChildren(); @@ -2885,8 +2880,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } private void focusNextViewIfFocused(View view) { - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (view instanceof ExpandableNotificationRow row) { if (row.shouldRefocusOnDismiss()) { View nextView = row.getChildAfterViewWhenDismissed(); if (nextView == null) { @@ -3034,8 +3028,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } private int getIntrinsicHeight(View view) { - if (view instanceof ExpandableView) { - ExpandableView expandableView = (ExpandableView) view; + if (view instanceof ExpandableView expandableView) { return expandableView.getIntrinsicHeight(); } return view.getHeight(); @@ -3125,8 +3118,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable generateAddAnimation(child, false /* fromMoreCard */); updateAnimationState(child); updateChronometerForChild(child); - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (child instanceof ExpandableNotificationRow row) { row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX); } @@ -3195,8 +3187,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } private void updateAnimationState(boolean running, View child) { - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (child instanceof ExpandableNotificationRow row) { row.setAnimationRunning(running); } } @@ -3323,8 +3314,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable logHunAnimationSkipped(row, "row has no viewState"); continue; } + boolean shouldHunAppearFromTheBottom = + mStackScrollAlgorithm.shouldHunAppearFromBottom(mAmbientState, viewState); if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) { - if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) { + if (pinnedAndClosed || shouldHunAppearFromTheBottom) { // Our custom add animation type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; } else { @@ -3336,6 +3329,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } AnimationEvent event = new AnimationEvent(row, type); event.headsUpFromBottom = onBottom; + if (NotificationsImprovedHunAnimation.isEnabled()) { + // TODO(b/283084712) remove this with the flag and update the HUN filters at + // creation + event.filter.animateHeight = false; + } mAnimationEvents.add(event); if (SPEW) { Log.v(TAG, "Generating HUN animation event: " @@ -3350,11 +3348,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAddedHeadsUpChildren.clear(); } - private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) { - return viewState.getYTranslation() + viewState.height - >= mAmbientState.getMaxHeadsUpTranslation(); - } - private void generateGroupExpansionEvent() { // Generate a group expansion/collapsing event if there is such a group at all if (mExpandedGroupView != null) { @@ -3391,8 +3384,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // we need to know the view after this one float removedTranslation = child.getTranslationY(); boolean ignoreChildren = true; - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (child instanceof ExpandableNotificationRow row) { if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) { removedTranslation = row.getTranslationWhenRemoved(); ignoreChildren = false; @@ -3434,8 +3426,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private void generatePositionChangeEvents() { for (ExpandableView child : mChildrenChangingPositions) { Integer duration = null; - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (child instanceof ExpandableNotificationRow row) { if (row.getEntry().isMarkedForUserTriggeredMovement()) { duration = StackStateAnimator.ANIMATION_DURATION_PRIORITY_CHANGE; row.getEntry().markForUserTriggeredMovement(false); @@ -4119,8 +4110,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private void clearUserLockedViews() { for (int i = 0; i < getChildCount(); i++) { ExpandableView child = getChildAtIndex(i); - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (child instanceof ExpandableNotificationRow row) { row.setUserLocked(false); } } @@ -4134,8 +4124,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable ); for (int i = 0; i < getChildCount(); i++) { ExpandableView child = getChildAtIndex(i); - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (child instanceof ExpandableNotificationRow row) { clearTemporaryViewsInGroup( /* viewGroup = */ row.getChildrenContainer(), /* reason = */ "clearTemporaryViewsInGroup(row.getChildrenContainer())" @@ -4220,8 +4209,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } void updateChronometerForChild(View child) { - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (child instanceof ExpandableNotificationRow row) { row.setChronometerRunning(mIsExpanded); } } @@ -4260,8 +4248,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } private void updateScrollPositionOnExpandInBottom(ExpandableView view) { - if (view instanceof ExpandableNotificationRow && !onKeyguard()) { - ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (view instanceof ExpandableNotificationRow row && !onKeyguard()) { // TODO: once we're recycling this will need to check the adapter position of the child if (row.isUserLocked() && row != getFirstChildNotGone()) { if (row.isSummaryWithChildren()) { @@ -4320,8 +4307,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private void clearHeadsUpDisappearRunning() { for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (view instanceof ExpandableNotificationRow row) { row.setHeadsUpAnimatingAway(false); if (row.isSummaryWithChildren()) { for (ExpandableNotificationRow child : row.getAttachedChildren()) { @@ -4918,7 +4904,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ public void setHeadsUpBoundaries(int height, int bottomBarHeight) { mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight); + mStackScrollAlgorithm.setHeadsUpAppearHeightBottom(height); mStateAnimator.setHeadsUpAppearHeightBottom(height); + mStateAnimator.setStackTopMargin(mAmbientState.getStackTopMargin()); requestChildrenUpdate(); } @@ -5203,8 +5191,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } View swipedView = mSwipeHelper.getSwipedView(); pw.println("Swiped view: " + swipedView); - if (swipedView instanceof ExpandableView) { - ExpandableView expandableView = (ExpandableView) swipedView; + if (swipedView instanceof ExpandableView expandableView) { expandableView.dump(pw, args); } }); @@ -5287,8 +5274,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (view instanceof SectionHeaderView && silentSectionWillBeGone) { return true; } - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (view instanceof ExpandableNotificationRow row) { if (isVisible(row) && includeChildInClearAll(row, selection)) { return true; } @@ -5314,9 +5300,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (shouldHideParent(view, selection)) { viewsToHide.add(view); } - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow parent = (ExpandableNotificationRow) view; - + if (view instanceof ExpandableNotificationRow parent) { if (isChildrenVisible(parent)) { for (ExpandableNotificationRow child : parent.getAttachedChildren()) { if (isVisible(child) && includeChildInClearAll(child, selection)) { @@ -5336,10 +5320,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable for (int i = 0; i < childCount; i++) { final View view = getChildAt(i); - if (!(view instanceof ExpandableNotificationRow)) { + if (!(view instanceof ExpandableNotificationRow parent)) { continue; } - ExpandableNotificationRow parent = (ExpandableNotificationRow) view; if (includeChildInClearAll(parent, selection)) { viewsToRemove.add(parent); } @@ -5978,8 +5961,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); mSwipeHelper.forceResetSwipeState(child); - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow childRow = (ExpandableNotificationRow) child; + if (child instanceof ExpandableNotificationRow childRow) { List<ExpandableNotificationRow> grandchildren = childRow.getAttachedChildren(); if (grandchildren != null) { for (ExpandableNotificationRow grandchild : grandchildren) { @@ -6265,8 +6247,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } static boolean canChildBeDismissed(View v) { - if (v instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (v instanceof ExpandableNotificationRow row) { if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) { return false; } @@ -6276,8 +6257,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } static boolean canChildBeCleared(View v) { - if (v instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (v instanceof ExpandableNotificationRow row) { if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) { return false; } @@ -6372,8 +6352,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /* Only ever called as a consequence of an expansion gesture in the shade. */ @Override public void setUserExpandedChild(View v, boolean userExpanded) { - if (v instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (v instanceof ExpandableNotificationRow row) { if (userExpanded && onKeyguard()) { // Due to a race when locking the screen while touching, a notification may be // expanded even after we went back to keyguard. An example of this happens if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index d2fca8fca837..6f5058c8c52e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -405,8 +405,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { if (!mAllowLongPress) { return; } - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (view instanceof ExpandableNotificationRow row) { mMetricsLogger.write(row.getEntry().getSbn().getLogMaker() .setCategory(MetricsEvent.ACTION_TOUCH_GEAR) .setType(MetricsEvent.TYPE_ACTION) @@ -426,8 +425,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onMenuShown(View row) { - if (row instanceof ExpandableNotificationRow) { - ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row; + if (row instanceof ExpandableNotificationRow notificationRow) { mMetricsLogger.write(notificationRow.getEntry().getSbn().getLogMaker() .setCategory(MetricsEvent.ACTION_REVEAL_GEAR) .setType(MetricsEvent.TYPE_ACTION)); @@ -492,10 +490,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { */ @Override public void onChildDismissed(View view) { - if (!(view instanceof ActivatableNotificationView)) { + if (!(view instanceof ActivatableNotificationView row)) { return; } - ActivatableNotificationView row = (ActivatableNotificationView) view; if (!row.isDismissed()) { handleChildViewDismissed(view); } @@ -519,8 +516,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { if (mView.getClearAllInProgress()) { return; } - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (view instanceof ExpandableNotificationRow row) { if (row.isHeadsUp()) { mHeadsUpManager.addSwipedOutNotification( row.getEntry().getSbn().getKey()); @@ -551,8 +547,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { ev.getY(), true /* requireMinHeight */, false /* ignoreDecors */); - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (child instanceof ExpandableNotificationRow row) { ExpandableNotificationRow parent = row.getNotificationParent(); if (parent != null && parent.areChildrenExpanded() && (parent.areGutsExposed() @@ -582,8 +577,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onChildSnappedBack(View animView, float targetLeft) { mView.onSwipeEnd(); - if (animView instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) animView; + if (animView instanceof ExpandableNotificationRow row) { if (row.isPinned() && !canChildBeDismissed(row) && row.getEntry().getSbn().getNotification().fullScreenIntent == null) { @@ -859,7 +853,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { mGroupExpansionManager.registerGroupExpansionChangeListener( (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded)); - mViewBinder.bind(mView, this); + mViewBinder.bindWhileAttached(mView, this); if (!FooterViewRefactor.isEnabled()) { collectFlow(mView, mKeyguardTransitionRepo.getTransitions(), @@ -1980,8 +1974,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { // Check if we need to clear any snooze leavebehinds if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts) - && guts.getGutsContent() instanceof NotificationSnooze) { - NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent(); + && guts.getGutsContent() instanceof NotificationSnooze ns) { if ((ns.isExpanded() && isCancelOrUp) || (!horizontalSwipeWantsIt && scrollerWantsIt)) { // If the leavebehind is expanded we clear it on the next up event, otherwise we diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 06ca9a50bb6d..664a6b6c54c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -37,6 +37,7 @@ import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import java.util.ArrayList; import java.util.List; @@ -66,12 +67,15 @@ public class StackScrollAlgorithm { private boolean mClipNotificationScrollToTop; @VisibleForTesting float mHeadsUpInset; + @VisibleForTesting + float mHeadsUpAppearStartAboveScreen; private int mPinnedZTranslationExtra; private float mNotificationScrimPadding; private int mMarginBottom; private float mQuickQsOffsetHeight; private float mSmallCornerRadius; private float mLargeCornerRadius; + private int mHeadsUpAppearHeightBottom; public StackScrollAlgorithm( Context context, @@ -94,6 +98,8 @@ public class StackScrollAlgorithm { int statusBarHeight = SystemBarUtils.getStatusBarHeight(context); mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize( R.dimen.heads_up_status_bar_padding); + mHeadsUpAppearStartAboveScreen = res.getDimensionPixelSize( + R.dimen.heads_up_appear_y_above_screen); mPinnedZTranslationExtra = res.getDimensionPixelSize( R.dimen.heads_up_pinned_elevation); mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); @@ -221,6 +227,25 @@ public class StackScrollAlgorithm { return getExpansionFractionWithoutShelf(mTempAlgorithmState, ambientState); } + public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) { + mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom; + } + + /** + * If the QuickSettings is showing full screen, we want to animate the HeadsUp Notifications + * from the bottom of the screen. + * + * @param ambientState Current ambient state. + * @param viewState The state of the HUN that is being queried to appear from the bottom. + * + * @return true if the HeadsUp Notifications should appear from the bottom + */ + public boolean shouldHunAppearFromBottom(AmbientState ambientState, + ExpandableViewState viewState) { + return viewState.getYTranslation() + viewState.height + >= ambientState.getMaxHeadsUpTranslation(); + } + public static void log(String s) { if (DEBUG) { android.util.Log.i(TAG, s); @@ -229,8 +254,7 @@ public class StackScrollAlgorithm { public static void logView(View view, String s) { String viewString = ""; - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = ((ExpandableNotificationRow) view); + if (view instanceof ExpandableNotificationRow row) { if (row.getEntry() == null) { viewString = "ExpandableNotificationRow has null NotificationEntry"; } else { @@ -264,8 +288,7 @@ public class StackScrollAlgorithm { int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { ExpandableView v = algorithmState.visibleChildren.get(i); - if (v instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (v instanceof ExpandableNotificationRow row) { row.updateChildrenStates(); } } @@ -376,8 +399,7 @@ public class StackScrollAlgorithm { continue; } notGoneIndex = updateNotGoneIndex(state, notGoneIndex, v); - if (v instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (v instanceof ExpandableNotificationRow row) { // handle the notGoneIndex for the children as well List<ExpandableNotificationRow> children = row.getAttachedChildren(); @@ -508,10 +530,9 @@ public class StackScrollAlgorithm { private boolean hasNonClearableNotifs(StackScrollAlgorithmState algorithmState) { for (int i = 0; i < algorithmState.visibleChildren.size(); i++) { View child = algorithmState.visibleChildren.get(i); - if (!(child instanceof ExpandableNotificationRow)) { + if (!(child instanceof ExpandableNotificationRow row)) { continue; } - final ExpandableNotificationRow row = (ExpandableNotificationRow) child; if (!row.canViewBeCleared()) { return true; } @@ -715,10 +736,9 @@ public class StackScrollAlgorithm { ExpandableNotificationRow pulsingRow = null; for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); - if (!(child instanceof ExpandableNotificationRow)) { + if (!(child instanceof ExpandableNotificationRow row)) { continue; } - ExpandableNotificationRow row = (ExpandableNotificationRow) child; if (!row.showingPulsing() || (i == 0 && ambientState.isPulseExpanding())) { continue; } @@ -760,10 +780,9 @@ public class StackScrollAlgorithm { ExpandableNotificationRow topHeadsUpEntry = null; for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); - if (!(child instanceof ExpandableNotificationRow)) { + if (!(child instanceof ExpandableNotificationRow row)) { continue; } - ExpandableNotificationRow row = (ExpandableNotificationRow) child; if (!(row.isHeadsUp() || row.isHeadsUpAnimatingAway())) { continue; } @@ -793,10 +812,16 @@ public class StackScrollAlgorithm { } } if (row.isPinned()) { - // Make sure row yTranslation is at maximum the HUN yTranslation, - // which accounts for AmbientState.stackTopMargin in split-shade. - childState.setYTranslation( - Math.max(childState.getYTranslation(), headsUpTranslation)); + if (NotificationsImprovedHunAnimation.isEnabled()) { + // Make sure row yTranslation is at the HUN yTranslation, + // which accounts for AmbientState.stackTopMargin in split-shade. + childState.setYTranslation(headsUpTranslation); + } else { + // Make sure row yTranslation is at maximum the HUN yTranslation, + // which accounts for AmbientState.stackTopMargin in split-shade. + childState.setYTranslation( + Math.max(childState.getYTranslation(), headsUpTranslation)); + } childState.height = Math.max(row.getIntrinsicHeight(), childState.height); childState.hidden = false; ExpandableViewState topState = @@ -819,10 +844,22 @@ public class StackScrollAlgorithm { } } if (row.isHeadsUpAnimatingAway()) { - // Make sure row yTranslation is at maximum the HUN yTranslation, - // which accounts for AmbientState.stackTopMargin in split-shade. - childState.setYTranslation( - Math.max(childState.getYTranslation(), headsUpTranslation)); + if (NotificationsImprovedHunAnimation.isEnabled()) { + if (shouldHunAppearFromBottom(ambientState, childState)) { + // move to the bottom of the screen + childState.setYTranslation( + mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen); + } else { + // move to the top of the screen + childState.setYTranslation(-ambientState.getStackTopMargin() + - mHeadsUpAppearStartAboveScreen); + } + } else { + // Make sure row yTranslation is at maximum the HUN yTranslation, + // which accounts for AmbientState.stackTopMargin in split-shade. + childState.setYTranslation( + Math.max(childState.getYTranslation(), headsUpTranslation)); + } // keep it visible for the animation childState.hidden = false; } @@ -897,8 +934,7 @@ public class StackScrollAlgorithm { } protected int getMaxAllowedChildHeight(View child) { - if (child instanceof ExpandableView) { - ExpandableView expandableView = (ExpandableView) child; + if (child instanceof ExpandableView expandableView) { return expandableView.getIntrinsicHeight(); } return child == null ? mCollapsedSize : child.getHeight(); 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 e94258f416ac..a3e09417b34c 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,7 @@ package com.android.systemui.statusbar.notification.stack; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; 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; @@ -26,6 +27,7 @@ import android.util.Property; import android.view.View; import com.android.app.animation.Interpolators; +import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardSliceView; import com.android.systemui.res.R; import com.android.systemui.shared.clocks.AnimatableClockView; @@ -33,6 +35,7 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; +import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import java.util.ArrayList; import java.util.HashSet; @@ -68,6 +71,8 @@ public class StackStateAnimator { private final int mGoToFullShadeAppearingTranslation; private final int mPulsingAppearingTranslation; + @VisibleForTesting + float mHeadsUpAppearStartAboveScreen; private final ExpandableViewState mTmpState = new ExpandableViewState(); private final AnimationProperties mAnimationProperties; public NotificationStackScrollLayout mHostLayout; @@ -85,21 +90,23 @@ public class StackStateAnimator { private ValueAnimator mTopOverScrollAnimator; private ValueAnimator mBottomOverScrollAnimator; private int mHeadsUpAppearHeightBottom; + private int mStackTopMargin; private boolean mShadeExpanded; private ArrayList<ExpandableView> mTransientViewsToRemove = new ArrayList<>(); private NotificationShelf mShelf; - private float mStatusBarIconLocation; - private int[] mTmpLocation = new int[2]; private StackStateLogger mLogger; public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; + // TODO(b/317061579) reload on configuration changes mGoToFullShadeAppearingTranslation = hostLayout.getContext().getResources().getDimensionPixelSize( R.dimen.go_to_full_shade_appearing_translation); mPulsingAppearingTranslation = hostLayout.getContext().getResources().getDimensionPixelSize( R.dimen.pulsing_notification_appear_translation); + mHeadsUpAppearStartAboveScreen = hostLayout.getContext().getResources() + .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen); mAnimationProperties = new AnimationProperties() { @Override public AnimationFilter getAnimationFilter() { @@ -455,8 +462,37 @@ public class StackStateAnimator { .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) { ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView; row.prepareExpansionChanged(); - } else if (event.animationType == NotificationStackScrollLayout - .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) { + } else if (NotificationsImprovedHunAnimation.isEnabled() + && (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) { + mHeadsUpAppearChildren.add(changingView); + + mTmpState.copyFrom(changingView.getViewState()); + if (event.headsUpFromBottom) { + // start from the bottom of the screen + mTmpState.setYTranslation( + mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen); + } else { + // start from the top of the screen + mTmpState.setYTranslation( + -mStackTopMargin - mHeadsUpAppearStartAboveScreen); + } + // set the height and the initial position + mTmpState.applyToView(changingView); + mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y, + Interpolators.FAST_OUT_SLOW_IN); + + Runnable onAnimationEnd = null; + if (loggable) { + // This only captures HEADS_UP_APPEAR animations, but HUNs can appear with + // normal ADD animations, which would not be logged here. + String finalKey = key; + mLogger.logHUNViewAppearing(key); + onAnimationEnd = () -> mLogger.appearAnimationEnded(finalKey); + } + changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR, + /* isHeadsUpAppear= */ true, onAnimationEnd); + } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) { + NotificationsImprovedHunAnimation.assertInLegacyMode(); // This item is added, initialize its properties. ExpandableViewState viewState = changingView.getViewState(); mTmpState.copyFrom(viewState); @@ -536,6 +572,10 @@ public class StackStateAnimator { changingView.setInRemovalAnimation(true); }; } + if (NotificationsImprovedHunAnimation.isEnabled()) { + mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y, + Interpolators.FAST_OUT_SLOW_IN_REVERSE); + } long removeAnimationDelay = changingView.performRemoveAnimation( ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 0, 0.0f, true /* isHeadsUpAppear */, @@ -601,6 +641,10 @@ public class StackStateAnimator { mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom; } + public void setStackTopMargin(int stackTopMargin) { + mStackTopMargin = stackTopMargin; + } + public void setShadeExpanded(boolean shadeExpanded) { mShadeExpanded = shadeExpanded; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt index 274bf94566cc..910b40f594f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt @@ -16,29 +16,22 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder import androidx.core.view.doOnDetach -import androidx.lifecycle.lifecycleScope -import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel -import kotlinx.coroutines.launch /** * Binds a [NotificationStackScrollLayoutController] to its [view model][NotificationListViewModel]. */ object HideNotificationsBinder { - fun bindHideList( + suspend fun bindHideList( viewController: NotificationStackScrollLayoutController, viewModel: NotificationListViewModel ) { - viewController.view.repeatWhenAttached { - lifecycleScope.launch { - viewModel.hideListViewModel.shouldHideListForPerformance.collect { shouldHide -> - viewController.bindHideState(shouldHide) - } - } - } - viewController.view.doOnDetach { viewController.bindHideState(shouldHide = false) } + + viewModel.hideListViewModel.shouldHideListForPerformance.collect { shouldHide -> + viewController.bindHideState(shouldHide) + } } private fun NotificationStackScrollLayoutController.bindHideState(shouldHide: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index 9373d497ffa7..1b3666078b27 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -32,15 +32,14 @@ import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel import com.android.systemui.statusbar.phone.NotificationIconAreaController -import com.android.systemui.statusbar.ui.SystemBarUtilsState +import com.android.systemui.util.kotlin.getOrNull import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.combine @@ -55,25 +54,27 @@ constructor( private val configuration: ConfigurationState, private val falsingManager: FalsingManager, private val iconAreaController: NotificationIconAreaController, - private val iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val metricsLogger: MetricsLogger, - private val shelfIconViewStore: ShelfNotificationIconViewStore, - private val systemBarUtilsState: SystemBarUtilsState, + private val nicBinder: NotificationIconContainerShelfViewBinder, ) { - fun bind( + fun bindWhileAttached( view: NotificationStackScrollLayout, viewController: NotificationStackScrollLayoutController ) { - bindShelf(view) - bindHideList(viewController, viewModel) + val shelf = + LayoutInflater.from(view.context) + .inflate(R.layout.status_bar_notification_shelf, view, false) as NotificationShelf + view.setShelf(shelf) - if (FooterViewRefactor.isEnabled) { - bindFooter(view) - bindEmptyShade(view) + view.repeatWhenAttached { + lifecycleScope.launch { + launch { bindShelf(shelf) } + launch { bindHideList(viewController, viewModel) } - view.repeatWhenAttached { - lifecycleScope.launch { + if (FooterViewRefactor.isEnabled) { + launch { bindFooter(view) } + launch { bindEmptyShade(view) } viewModel.isImportantForAccessibility.collect { isImportantForAccessibility -> view.setImportantForAccessibilityYesNo(isImportantForAccessibility) } @@ -82,73 +83,57 @@ constructor( } } - private fun bindShelf(parentView: NotificationStackScrollLayout) { - val shelf = - LayoutInflater.from(parentView.context) - .inflate(R.layout.status_bar_notification_shelf, parentView, false) - as NotificationShelf + private suspend fun bindShelf(shelf: NotificationShelf) { NotificationShelfViewBinder.bind( shelf, viewModel.shelf, - configuration, - systemBarUtilsState, falsingManager, - iconViewBindingFailureTracker, + nicBinder, iconAreaController, - shelfIconViewStore, ) - parentView.setShelf(shelf) } - private fun bindFooter(parentView: NotificationStackScrollLayout) { - viewModel.footer.ifPresent { footerViewModel -> + private suspend fun bindFooter(parentView: NotificationStackScrollLayout) { + viewModel.footer.getOrNull()?.let { footerViewModel -> // The footer needs to be re-inflated every time the theme or the font size changes. - parentView.repeatWhenAttached { - configuration.reinflateAndBindLatest( - R.layout.status_bar_notification_footer, - parentView, - attachToRoot = false, - backgroundDispatcher, - ) { footerView: FooterView -> - traceSection("bind FooterView") { - val disposableHandle = - FooterViewBinder.bind( - footerView, - footerViewModel, - clearAllNotifications = { - metricsLogger.action( - MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES - ) - parentView.clearAllNotifications() - }, - ) - parentView.setFooterView(footerView) - return@reinflateAndBindLatest disposableHandle - } + configuration.reinflateAndBindLatest( + R.layout.status_bar_notification_footer, + parentView, + attachToRoot = false, + backgroundDispatcher, + ) { footerView: FooterView -> + traceSection("bind FooterView") { + val disposableHandle = + FooterViewBinder.bind( + footerView, + footerViewModel, + clearAllNotifications = { + metricsLogger.action( + MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES + ) + parentView.clearAllNotifications() + }, + ) + parentView.setFooterView(footerView) + return@reinflateAndBindLatest disposableHandle } } } } - private fun bindEmptyShade( - parentView: NotificationStackScrollLayout, - ) { - parentView.repeatWhenAttached { - lifecycleScope.launch { - combine( - viewModel.shouldShowEmptyShadeView, - viewModel.areNotificationsHiddenInShade, - viewModel.hasFilteredOutSeenNotifications, - ::Triple - ) - .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) -> - parentView.updateEmptyShadeView( - shouldShow, - areNotifsHidden, - hasFilteredNotifs, - ) - } + private suspend fun bindEmptyShade(parentView: NotificationStackScrollLayout) { + combine( + viewModel.shouldShowEmptyShadeView, + viewModel.areNotificationsHiddenInShade, + viewModel.hasFilteredOutSeenNotifications, + ::Triple + ) + .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) -> + parentView.updateEmptyShadeView( + shouldShow, + areNotifsHidden, + hasFilteredNotifs, + ) } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 145dbff81144..7cc08887ae52 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -196,7 +196,7 @@ public class KeyguardStatusBarView extends RelativeLayout { } private void updateKeyguardStatusBarHeight() { - MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); + ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams(); lp.height = getStatusBarHeaderHeightKeyguard(mContext); setLayoutParams(lp); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index cd999349d055..2740cc6682a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -38,7 +38,6 @@ import com.android.app.animation.Interpolators; import com.android.app.animation.InterpolatorsAndroidX; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; -import com.android.systemui.common.ui.ConfigurationState; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; @@ -54,10 +53,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore; -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; @@ -75,7 +71,6 @@ import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusB import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener; import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.ui.SystemBarUtilsState; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateListener; import com.android.systemui.util.CarrierConfigTracker; @@ -95,6 +90,8 @@ import javax.inject.Inject; import kotlin.Unit; +import kotlinx.coroutines.DisposableHandle; + /** * Contains the collapsed status bar and handles hiding/showing based on disable flags * and keyguard state. Also manages lifecycle to make sure the views it contains are being @@ -151,10 +148,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final DumpManager mDumpManager; private final StatusBarWindowStateController mStatusBarWindowStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final NotificationIconContainerStatusBarViewModel mStatusBarIconsViewModel; - private final ConfigurationState mConfigurationState; - private final SystemBarUtilsState mSystemBarUtilsState; - private final StatusBarNotificationIconViewStore mStatusBarIconViewStore; + private final NotificationIconContainerStatusBarViewBinder mNicViewBinder; private final DemoModeController mDemoModeController; private List<String> mBlockedIcons = new ArrayList<>(); @@ -216,7 +210,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mWaitingForWindowStateChangeAfterCameraLaunch = false; mTransitionFromLockscreenToDreamStarted = false; }; - private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker; + private DisposableHandle mNicBindingDisposable; @Inject public CollapsedStatusBarFragment( @@ -234,7 +228,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue KeyguardStateController keyguardStateController, ShadeViewController shadeViewController, StatusBarStateController statusBarStateController, - StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker, + NotificationIconContainerStatusBarViewBinder nicViewBinder, CommandQueue commandQueue, CarrierConfigTracker carrierConfigTracker, CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, @@ -244,10 +238,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue DumpManager dumpManager, StatusBarWindowStateController statusBarWindowStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - NotificationIconContainerStatusBarViewModel statusBarIconsViewModel, - ConfigurationState configurationState, - SystemBarUtilsState systemBarUtilsState, - StatusBarNotificationIconViewStore statusBarIconViewStore, DemoModeController demoModeController) { mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory; mOngoingCallController = ongoingCallController; @@ -263,7 +253,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mKeyguardStateController = keyguardStateController; mShadeViewController = shadeViewController; mStatusBarStateController = statusBarStateController; - mIconViewBindingFailureTracker = iconViewBindingFailureTracker; + mNicViewBinder = nicViewBinder; mCommandQueue = commandQueue; mCarrierConfigTracker = carrierConfigTracker; mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger; @@ -273,10 +263,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mDumpManager = dumpManager; mStatusBarWindowStateController = statusBarWindowStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mStatusBarIconsViewModel = statusBarIconsViewModel; - mConfigurationState = configurationState; - mSystemBarUtilsState = systemBarUtilsState; - mStatusBarIconViewStore = statusBarIconViewStore; mDemoModeController = demoModeController; } @@ -455,6 +441,12 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStartableStates.put(startable, Startable.State.STOPPED); } mDumpManager.unregisterDumpable(getClass().getSimpleName()); + if (NotificationIconContainerRefactor.isEnabled()) { + if (mNicBindingDisposable != null) { + mNicBindingDisposable.dispose(); + mNicBindingDisposable = null; + } + } } /** Initializes views related to the notification icon area. */ @@ -466,13 +458,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue .inflate(R.layout.notification_icon_area, notificationIconArea, true); NotificationIconContainer notificationIcons = notificationIconArea.requireViewById(R.id.notificationIcons); - NotificationIconContainerViewBinder.bindWhileAttached( - notificationIcons, - mStatusBarIconsViewModel, - mConfigurationState, - mSystemBarUtilsState, - mIconViewBindingFailureTracker, - mStatusBarIconViewStore); + mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons); } else { mNotificationIconAreaInner = mNotificationIconAreaController.getNotificationInnerAreaView(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 4864fb8ca634..5bced934be7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -303,8 +303,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mEntry.mRemoteEditImeVisible = editTextRootWindowInsets != null && editTextRootWindowInsets.isVisible(WindowInsets.Type.ime()); if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) { - // Pass null to ensure all inputs are cleared for this entry b/227115380 - mController.removeRemoteInput(mEntry, null, + mController.removeRemoteInput(mEntry, mToken, /* reason= */"RemoteInputView$WindowInsetAnimation#onEnd"); } } @@ -536,6 +535,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) { return; } + // RemoteInputView can be detached from window before IME close event in some cases like + // remote input view removal with notification update. As a result of this, RemoteInputView + // will stop ime animation updates, which results in never removing remote input. That's why + // we have to set mRemoteEditImeAnimatingAway false on detach to remove remote input. + mEntry.mRemoteEditImeAnimatingAway = false; mController.removeRemoteInput(mEntry, mToken, /* reason= */"RemoteInputView#onDetachedFromWindow"); mController.removeSpinning(mEntry.getKey(), mToken); diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt index e0d205fc4b6a..c170eb567344 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt @@ -37,6 +37,7 @@ import com.android.internal.logging.UiEventLogger import com.android.internal.util.UserIcons import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.systemui.Flags.switchUserOnBg import com.android.systemui.SystemUISecondaryUserService import com.android.systemui.animation.Expandable import com.android.systemui.broadcast.BroadcastDispatcher @@ -44,6 +45,7 @@ import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -100,6 +102,7 @@ constructor( broadcastDispatcher: BroadcastDispatcher, keyguardUpdateMonitor: KeyguardUpdateMonitor, @Background private val backgroundDispatcher: CoroutineDispatcher, + @Main private val mainDispatcher: CoroutineDispatcher, private val activityManager: ActivityManager, private val refreshUsersScheduler: RefreshUsersScheduler, private val guestUserInteractor: GuestUserInteractor, @@ -339,7 +342,11 @@ constructor( } .launchIn(applicationScope) restartSecondaryService(repository.getSelectedUserInfo().id) - keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + applicationScope.launch { + withContext(mainDispatcher) { + keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + } + } } fun addCallback(callback: UserCallback) { @@ -593,10 +600,18 @@ constructor( private fun switchUser(userId: Int) { // TODO(b/246631653): track jank and latency like in the old impl. refreshUsersScheduler.pause() - try { - activityManager.switchUser(userId) - } catch (e: RemoteException) { - Log.e(TAG, "Couldn't switch user.", e) + val runnable = Runnable { + try { + activityManager.switchUser(userId) + } catch (e: RemoteException) { + Log.e(TAG, "Couldn't switch user.", e) + } + } + + if (switchUserOnBg()) { + applicationScope.launch { withContext(backgroundDispatcher) { runnable.run() } } + } else { + runnable.run() } } diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java b/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java deleted file mode 100644 index 82153600e473..000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2017 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.leak; - -import android.content.ClipData; -import android.content.ClipDescription; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.util.Log; - -import androidx.core.content.FileProvider; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -/** - * Utility class for dumping, compressing, sending, and serving heap dump files. - * - * <p>Unlike the Internet, this IS a big truck you can dump something on. - */ -public class DumpTruck { - private static final String FILEPROVIDER_AUTHORITY = "com.android.systemui.fileprovider"; - private static final String FILEPROVIDER_PATH = "leak"; - - private static final String TAG = "DumpTruck"; - private static final int BUFSIZ = 1024 * 1024; // 1MB - - private final Context context; - private final GarbageMonitor mGarbageMonitor; - private Uri hprofUri; - private long rss; - final StringBuilder body = new StringBuilder(); - - public DumpTruck(Context context, GarbageMonitor garbageMonitor) { - this.context = context; - mGarbageMonitor = garbageMonitor; - } - - /** - * Capture memory for the given processes and zip them up for sharing. - * - * @param pids - * @return this, for chaining - */ - public DumpTruck captureHeaps(List<Long> pids) { - final File dumpDir = new File(context.getCacheDir(), FILEPROVIDER_PATH); - dumpDir.mkdirs(); - hprofUri = null; - - body.setLength(0); - body.append("Build: ").append(Build.DISPLAY).append("\n\nProcesses:\n"); - - final ArrayList<String> paths = new ArrayList<String>(); - final int myPid = android.os.Process.myPid(); - - for (Long pidL : pids) { - final int pid = pidL.intValue(); - body.append(" pid ").append(pid); - GarbageMonitor.ProcessMemInfo info = mGarbageMonitor.getMemInfo(pid); - if (info != null) { - body.append(":") - .append(" up=") - .append(info.getUptime()) - .append(" rss=") - .append(info.currentRss); - rss = info.currentRss; - } - if (pid == myPid) { - final String path = - new File(dumpDir, String.format("heap-%d.ahprof", pid)).getPath(); - Log.v(TAG, "Dumping memory info for process " + pid + " to " + path); - try { - android.os.Debug.dumpHprofData(path); // will block - paths.add(path); - body.append(" (hprof attached)"); - } catch (IOException e) { - Log.e(TAG, "error dumping memory:", e); - body.append("\n** Could not dump heap: \n").append(e.toString()).append("\n"); - } - } - body.append("\n"); - } - - try { - final String zipfile = - new File(dumpDir, String.format("hprof-%d.zip", System.currentTimeMillis())) - .getCanonicalPath(); - if (DumpTruck.zipUp(zipfile, paths)) { - final File pathFile = new File(zipfile); - hprofUri = FileProvider.getUriForFile(context, FILEPROVIDER_AUTHORITY, pathFile); - Log.v(TAG, "Heap dump accessible at URI: " + hprofUri); - } - } catch (IOException e) { - Log.e(TAG, "unable to zip up heapdumps", e); - body.append("\n** Could not zip up files: \n").append(e.toString()).append("\n"); - } - - return this; - } - - /** - * Get the Uri of the current heap dump. Be sure to call captureHeaps first. - * - * @return Uri to the dump served by the SystemUI file provider - */ - public Uri getDumpUri() { - return hprofUri; - } - - /** - * Get an ACTION_SEND intent suitable for startActivity() or attaching to a Notification. - * - * @return share intent - */ - public Intent createShareIntent() { - Intent shareIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); - shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - shareIntent.putExtra(Intent.EXTRA_SUBJECT, - String.format("SystemUI memory dump (rss=%dM)", rss / 1024)); - - shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString()); - - if (hprofUri != null) { - final ArrayList<Uri> uriList = new ArrayList<>(); - uriList.add(hprofUri); - shareIntent.setType("application/zip"); - shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList); - - // Include URI in ClipData also, so that grantPermission picks it up. - // We don't use setData here because some apps interpret this as "to:". - ClipData clipdata = new ClipData(new ClipDescription("content", - new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}), - new ClipData.Item(hprofUri)); - shareIntent.setClipData(clipdata); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - return shareIntent; - } - - private static boolean zipUp(String zipfilePath, ArrayList<String> paths) { - try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipfilePath))) { - final byte[] buf = new byte[BUFSIZ]; - - for (String filename : paths) { - try (InputStream is = new BufferedInputStream(new FileInputStream(filename))) { - ZipEntry entry = new ZipEntry(filename); - zos.putNextEntry(entry); - int len; - while (0 < (len = is.read(buf, 0, BUFSIZ))) { - zos.write(buf, 0, len); - } - zos.closeEntry(); - } - } - return true; - } catch (IOException e) { - Log.e(TAG, "error zipping up profile data", e); - } - return false; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java deleted file mode 100644 index de392d3f444f..000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java +++ /dev/null @@ -1,617 +0,0 @@ -/* - * Copyright (C) 2017 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.leak; - -import static android.service.quicksettings.Tile.STATE_ACTIVE; -import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE; - -import static com.android.internal.logging.MetricsLogger.VIEW_UNKNOWN; - -import android.annotation.Nullable; -import android.app.ActivityManager; -import android.content.Context; -import android.content.Intent; -import android.content.res.ColorStateList; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.Process; -import android.os.SystemProperties; -import android.provider.Settings; -import android.text.format.DateUtils; -import android.util.Log; -import android.util.LongSparseArray; -import android.view.View; - -import com.android.internal.logging.MetricsLogger; -import com.android.systemui.CoreStartable; -import com.android.systemui.Dumpable; -import com.android.systemui.res.R; -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.plugins.ActivityStarter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.QSHost; -import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; -import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.util.concurrency.DelayableExecutor; -import com.android.systemui.util.concurrency.MessageRouter; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -/** - * Suite of tools to periodically inspect the System UI heap and possibly prompt the user to - * capture heap dumps and report them. Includes the implementation of the "Dump SysUI Heap" - * quick settings tile. - */ -@SysUISingleton -public class GarbageMonitor implements Dumpable { - // Feature switches - // ================ - - // Whether to use TrackedGarbage to trigger LeakReporter. Off by default unless you set the - // appropriate sysprop on a userdebug device. - public static final boolean LEAK_REPORTING_ENABLED = Build.IS_DEBUGGABLE - && SystemProperties.getBoolean("debug.enable_leak_reporting", false); - public static final String FORCE_ENABLE_LEAK_REPORTING = "sysui_force_enable_leak_reporting"; - - // Heap tracking: watch the current memory levels and update the MemoryTile if available. - // On for all userdebug devices. - public static final boolean HEAP_TRACKING_ENABLED = Build.IS_DEBUGGABLE; - - // Tell QSTileHost.java to toss this into the default tileset? - public static final boolean ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS = true; - - // whether to use ActivityManager.setHeapLimit (and post a notification to the user asking - // to dump the heap). Off by default unless you set the appropriate sysprop on userdebug - private static final boolean ENABLE_AM_HEAP_LIMIT = Build.IS_DEBUGGABLE - && SystemProperties.getBoolean("debug.enable_sysui_heap_limit", false); - - // Tuning params - // ============= - - // threshold for setHeapLimit(), in KB (overrides R.integer.watch_heap_limit) - private static final String SETTINGS_KEY_AM_HEAP_LIMIT = "systemui_am_heap_limit"; - - private static final long GARBAGE_INSPECTION_INTERVAL = - 15 * DateUtils.MINUTE_IN_MILLIS; // 15 min - private static final long HEAP_TRACK_INTERVAL = 1 * DateUtils.MINUTE_IN_MILLIS; // 1 min - private static final int HEAP_TRACK_HISTORY_LEN = 720; // 12 hours - - private static final int DO_GARBAGE_INSPECTION = 1000; - private static final int DO_HEAP_TRACK = 3000; - - static final int GARBAGE_ALLOWANCE = 5; - - private static final String TAG = "GarbageMonitor"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private final MessageRouter mMessageRouter; - private final TrackedGarbage mTrackedGarbage; - private final LeakReporter mLeakReporter; - private final Context mContext; - private final DelayableExecutor mDelayableExecutor; - private MemoryTile mQSTile; - private final DumpTruck mDumpTruck; - - private final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<>(); - private final ArrayList<Long> mPids = new ArrayList<>(); - - private long mHeapLimit; - - /** - */ - @Inject - public GarbageMonitor( - Context context, - @Background DelayableExecutor delayableExecutor, - @Background MessageRouter messageRouter, - LeakDetector leakDetector, - LeakReporter leakReporter, - DumpManager dumpManager) { - mContext = context.getApplicationContext(); - - mDelayableExecutor = delayableExecutor; - mMessageRouter = messageRouter; - mMessageRouter.subscribeTo(DO_GARBAGE_INSPECTION, this::doGarbageInspection); - mMessageRouter.subscribeTo(DO_HEAP_TRACK, this::doHeapTrack); - - mTrackedGarbage = leakDetector.getTrackedGarbage(); - mLeakReporter = leakReporter; - - mDumpTruck = new DumpTruck(mContext, this); - - dumpManager.registerDumpable(getClass().getSimpleName(), this); - - if (ENABLE_AM_HEAP_LIMIT) { - mHeapLimit = Settings.Global.getInt(context.getContentResolver(), - SETTINGS_KEY_AM_HEAP_LIMIT, - mContext.getResources().getInteger(R.integer.watch_heap_limit)); - } - } - - public void startLeakMonitor() { - if (mTrackedGarbage == null) { - return; - } - - mMessageRouter.sendMessage(DO_GARBAGE_INSPECTION); - } - - public void startHeapTracking() { - startTrackingProcess( - android.os.Process.myPid(), mContext.getPackageName(), System.currentTimeMillis()); - mMessageRouter.sendMessage(DO_HEAP_TRACK); - } - - private boolean gcAndCheckGarbage() { - if (mTrackedGarbage.countOldGarbage() > GARBAGE_ALLOWANCE) { - Runtime.getRuntime().gc(); - return true; - } - return false; - } - - void reinspectGarbageAfterGc() { - int count = mTrackedGarbage.countOldGarbage(); - if (count > GARBAGE_ALLOWANCE) { - mLeakReporter.dumpLeak(count); - } - } - - public ProcessMemInfo getMemInfo(int pid) { - return mData.get(pid); - } - - public List<Long> getTrackedProcesses() { - return mPids; - } - - public void startTrackingProcess(long pid, String name, long start) { - synchronized (mPids) { - if (mPids.contains(pid)) return; - - mPids.add(pid); - logPids(); - - mData.put(pid, new ProcessMemInfo(pid, name, start)); - } - } - - private void logPids() { - if (DEBUG) { - StringBuffer sb = new StringBuffer("Now tracking processes: "); - for (int i = 0; i < mPids.size(); i++) { - final int p = mPids.get(i).intValue(); - sb.append(" "); - } - Log.v(TAG, sb.toString()); - } - } - - private void update() { - synchronized (mPids) { - for (int i = 0; i < mPids.size(); i++) { - final int pid = mPids.get(i).intValue(); - // rssValues contains [VmRSS, RssFile, RssAnon, VmSwap]. - long[] rssValues = Process.getRss(pid); - if (rssValues == null && rssValues.length == 0) { - if (DEBUG) Log.e(TAG, "update: Process.getRss() didn't provide any values."); - break; - } - long rss = rssValues[0]; - final ProcessMemInfo info = mData.get(pid); - info.rss[info.head] = info.currentRss = rss; - info.head = (info.head + 1) % info.rss.length; - if (info.currentRss > info.max) info.max = info.currentRss; - if (info.currentRss == 0) { - if (DEBUG) Log.v(TAG, "update: pid " + pid + " has rss=0, it probably died"); - mData.remove(pid); - } - } - for (int i = mPids.size() - 1; i >= 0; i--) { - final long pid = mPids.get(i).intValue(); - if (mData.get(pid) == null) { - mPids.remove(i); - logPids(); - } - } - } - if (mQSTile != null) mQSTile.update(); - } - - private void setTile(MemoryTile tile) { - mQSTile = tile; - if (tile != null) tile.update(); - } - - private static String formatBytes(long b) { - String[] SUFFIXES = {"B", "K", "M", "G", "T"}; - int i; - for (i = 0; i < SUFFIXES.length; i++) { - if (b < 1024) break; - b /= 1024; - } - return b + SUFFIXES[i]; - } - - private Intent dumpHprofAndGetShareIntent() { - return mDumpTruck.captureHeaps(getTrackedProcesses()).createShareIntent(); - } - - @Override - public void dump(PrintWriter pw, @Nullable String[] args) { - pw.println("GarbageMonitor params:"); - pw.println(String.format(" mHeapLimit=%d KB", mHeapLimit)); - pw.println(String.format(" GARBAGE_INSPECTION_INTERVAL=%d (%.1f mins)", - GARBAGE_INSPECTION_INTERVAL, - (float) GARBAGE_INSPECTION_INTERVAL / DateUtils.MINUTE_IN_MILLIS)); - final float htiMins = HEAP_TRACK_INTERVAL / DateUtils.MINUTE_IN_MILLIS; - pw.println(String.format(" HEAP_TRACK_INTERVAL=%d (%.1f mins)", - HEAP_TRACK_INTERVAL, - htiMins)); - pw.println(String.format(" HEAP_TRACK_HISTORY_LEN=%d (%.1f hr total)", - HEAP_TRACK_HISTORY_LEN, - (float) HEAP_TRACK_HISTORY_LEN * htiMins / 60f)); - - pw.println("GarbageMonitor tracked processes:"); - - for (long pid : mPids) { - final ProcessMemInfo pmi = mData.get(pid); - if (pmi != null) { - pmi.dump(pw, args); - } - } - } - - - private static class MemoryIconDrawable extends Drawable { - long rss, limit; - final Drawable baseIcon; - final Paint paint = new Paint(); - final float dp; - - MemoryIconDrawable(Context context) { - baseIcon = context.getDrawable(R.drawable.ic_memory).mutate(); - dp = context.getResources().getDisplayMetrics().density; - paint.setColor(Color.WHITE); - } - - public void setRss(long rss) { - if (rss != this.rss) { - this.rss = rss; - invalidateSelf(); - } - } - - public void setLimit(long limit) { - if (limit != this.limit) { - this.limit = limit; - invalidateSelf(); - } - } - - @Override - public void draw(Canvas canvas) { - baseIcon.draw(canvas); - - if (limit > 0 && rss > 0) { - float frac = Math.min(1f, (float) rss / limit); - - final Rect bounds = getBounds(); - canvas.translate(bounds.left + 8 * dp, bounds.top + 5 * dp); - //android:pathData="M16.0,5.0l-8.0,0.0l0.0,14.0l8.0,0.0z" - canvas.drawRect(0, 14 * dp * (1 - frac), 8 * dp + 1, 14 * dp + 1, paint); - } - } - - @Override - public void setBounds(int left, int top, int right, int bottom) { - super.setBounds(left, top, right, bottom); - baseIcon.setBounds(left, top, right, bottom); - } - - @Override - public int getIntrinsicHeight() { - return baseIcon.getIntrinsicHeight(); - } - - @Override - public int getIntrinsicWidth() { - return baseIcon.getIntrinsicWidth(); - } - - @Override - public void setAlpha(int i) { - baseIcon.setAlpha(i); - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - baseIcon.setColorFilter(colorFilter); - paint.setColorFilter(colorFilter); - } - - @Override - public void setTint(int tint) { - super.setTint(tint); - baseIcon.setTint(tint); - } - - @Override - public void setTintList(ColorStateList tint) { - super.setTintList(tint); - baseIcon.setTintList(tint); - } - - @Override - public void setTintMode(PorterDuff.Mode tintMode) { - super.setTintMode(tintMode); - baseIcon.setTintMode(tintMode); - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - } - - private static class MemoryGraphIcon extends QSTile.Icon { - long rss, limit; - - public void setRss(long rss) { - this.rss = rss; - } - - public void setHeapLimit(long limit) { - this.limit = limit; - } - - @Override - public Drawable getDrawable(Context context) { - final MemoryIconDrawable drawable = new MemoryIconDrawable(context); - drawable.setRss(rss); - drawable.setLimit(limit); - return drawable; - } - } - - public static class MemoryTile extends QSTileImpl<QSTile.State> { - public static final String TILE_SPEC = "dbg:mem"; - - private final GarbageMonitor gm; - private ProcessMemInfo pmi; - private boolean dumpInProgress; - private final PanelInteractor mPanelInteractor; - - @Inject - public MemoryTile( - QSHost host, - QsEventLogger uiEventLogger, - @Background Looper backgroundLooper, - @Main Handler mainHandler, - FalsingManager falsingManager, - MetricsLogger metricsLogger, - StatusBarStateController statusBarStateController, - ActivityStarter activityStarter, - QSLogger qsLogger, - GarbageMonitor monitor, - PanelInteractor panelInteractor - ) { - super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, - statusBarStateController, activityStarter, qsLogger); - gm = monitor; - mPanelInteractor = panelInteractor; - } - - @Override - public State newTileState() { - return new QSTile.State(); - } - - @Override - public Intent getLongClickIntent() { - return new Intent(); - } - - @Override - protected void handleClick(@Nullable View view) { - if (dumpInProgress) return; - - dumpInProgress = true; - refreshState(); - new Thread("HeapDumpThread") { - @Override - public void run() { - try { - // wait for animations & state changes - Thread.sleep(500); - } catch (InterruptedException ignored) { } - final Intent shareIntent = gm.dumpHprofAndGetShareIntent(); - mHandler.post(() -> { - dumpInProgress = false; - refreshState(); - mPanelInteractor.collapsePanels(); - mActivityStarter.postStartActivityDismissingKeyguard(shareIntent, 0); - }); - } - }.start(); - } - - @Override - public int getMetricsCategory() { - return VIEW_UNKNOWN; - } - - @Override - public void handleSetListening(boolean listening) { - super.handleSetListening(listening); - if (gm != null) gm.setTile(listening ? this : null); - - final ActivityManager am = mContext.getSystemService(ActivityManager.class); - if (listening && gm.mHeapLimit > 0) { - am.setWatchHeapLimit(1024 * gm.mHeapLimit); // why is this in bytes? - } else { - am.clearWatchHeapLimit(); - } - } - - @Override - public CharSequence getTileLabel() { - return getState().label; - } - - @Override - protected void handleUpdateState(State state, Object arg) { - pmi = gm.getMemInfo(Process.myPid()); - final MemoryGraphIcon icon = new MemoryGraphIcon(); - icon.setHeapLimit(gm.mHeapLimit); - state.state = dumpInProgress ? STATE_UNAVAILABLE : STATE_ACTIVE; - state.label = dumpInProgress - ? "Dumping..." - : mContext.getString(R.string.heap_dump_tile_name); - if (pmi != null) { - icon.setRss(pmi.currentRss); - state.secondaryLabel = - String.format( - "rss: %s / %s", - formatBytes(pmi.currentRss * 1024), - formatBytes(gm.mHeapLimit * 1024)); - } else { - icon.setRss(0); - state.secondaryLabel = null; - } - state.icon = icon; - } - - public void update() { - refreshState(); - } - - public long getRss() { - return pmi != null ? pmi.currentRss : 0; - } - - public long getHeapLimit() { - return gm != null ? gm.mHeapLimit : 0; - } - } - - /** */ - public static class ProcessMemInfo implements Dumpable { - public long pid; - public String name; - public long startTime; - public long currentRss; - public long[] rss = new long[HEAP_TRACK_HISTORY_LEN]; - public long max = 1; - public int head = 0; - - public ProcessMemInfo(long pid, String name, long start) { - this.pid = pid; - this.name = name; - this.startTime = start; - } - - public long getUptime() { - return System.currentTimeMillis() - startTime; - } - - @Override - public void dump(PrintWriter pw, @Nullable String[] args) { - pw.print("{ \"pid\": "); - pw.print(pid); - pw.print(", \"name\": \""); - pw.print(name.replace('"', '-')); - pw.print("\", \"start\": "); - pw.print(startTime); - pw.print(", \"rss\": ["); - // write rss values starting from the oldest, which is rss[head], wrapping around to - // rss[(head-1) % rss.length] - for (int i = 0; i < rss.length; i++) { - if (i > 0) pw.print(","); - pw.print(rss[(head + i) % rss.length]); - } - pw.println("] }"); - } - } - - /** */ - @SysUISingleton - public static class Service implements CoreStartable, Dumpable { - private final Context mContext; - private final GarbageMonitor mGarbageMonitor; - - @Inject - public Service(Context context, GarbageMonitor garbageMonitor) { - mContext = context; - mGarbageMonitor = garbageMonitor; - } - - @Override - public void start() { - boolean forceEnable = - Settings.Secure.getInt( - mContext.getContentResolver(), FORCE_ENABLE_LEAK_REPORTING, 0) - != 0; - if (LEAK_REPORTING_ENABLED || forceEnable) { - mGarbageMonitor.startLeakMonitor(); - } - if (HEAP_TRACKING_ENABLED || forceEnable) { - mGarbageMonitor.startHeapTracking(); - } - } - - @Override - public void dump(PrintWriter pw, @Nullable String[] args) { - if (mGarbageMonitor != null) mGarbageMonitor.dump(pw, args); - } - } - - private void doGarbageInspection(int id) { - if (gcAndCheckGarbage()) { - mDelayableExecutor.executeDelayed(this::reinspectGarbageAfterGc, 100); - } - - mMessageRouter.cancelMessages(DO_GARBAGE_INSPECTION); - mMessageRouter.sendMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL); - } - - private void doHeapTrack(int id) { - update(); - mMessageRouter.cancelMessages(DO_HEAP_TRACK); - mMessageRouter.sendMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt deleted file mode 100644 index e975200e4d52..000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util.leak - -import com.android.systemui.CoreStartable -import com.android.systemui.qs.tileimpl.QSTileImpl -import dagger.Binds -import dagger.Module -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap -import dagger.multibindings.StringKey - -@Module -interface GarbageMonitorModule { - /** Inject into GarbageMonitor.Service. */ - @Binds - @IntoMap - @ClassKey(GarbageMonitor::class) - fun bindGarbageMonitorService(sysui: GarbageMonitor.Service): CoreStartable - - @Binds - @IntoMap - @StringKey(GarbageMonitor.MemoryTile.TILE_SPEC) - fun bindMemoryTile(memoryTile: GarbageMonitor.MemoryTile): QSTileImpl<*> -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java index 88f63ad9c8cb..a2499615e18c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java @@ -34,14 +34,12 @@ import android.widget.LinearLayout; import android.widget.RelativeLayout; import com.android.systemui.SysuiTestCase; -import com.android.systemui.common.ui.ConfigurationState; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager; -import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel; import com.android.systemui.log.LogBuffer; import com.android.systemui.plugins.clocks.ClockAnimations; import com.android.systemui.plugins.clocks.ClockController; @@ -56,14 +54,9 @@ import com.android.systemui.shared.clocks.AnimatableClockView; import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel; -import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; -import com.android.systemui.statusbar.phone.ScreenOffAnimationController; -import com.android.systemui.statusbar.ui.SystemBarUtilsState; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; @@ -185,9 +178,7 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { mKeyguardSliceViewController, mNotificationIconAreaController, mSmartspaceController, - mock(SystemBarUtilsState.class), - mock(ScreenOffAnimationController.class), - mock(StatusBarIconViewBindingFailureTracker.class), + mock(NotificationIconContainerAlwaysOnDisplayViewBinder.class), mKeyguardUnlockAnimationController, mSecureSettings, mExecutor, @@ -195,11 +186,6 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { mDumpManager, mClockEventController, mLogBuffer, - mock(NotificationIconContainerAlwaysOnDisplayViewModel.class), - mock(KeyguardRootViewModel.class), - mock(ConfigurationState.class), - mock(DozeParameters.class), - mock(AlwaysOnDisplayNotificationIconViewStore.class), KeyguardInteractorFactory.create(mFakeFeatureFlags).getKeyguardInteractor(), mKeyguardClockInteractor, mFakeFeatureFlags, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt index 27d93eb31922..8f0e910271c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt @@ -24,7 +24,6 @@ import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR -import android.platform.test.annotations.RequiresFlagsEnabled import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.shared.model.AuthenticationReason @@ -48,7 +47,6 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule -@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR) @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) @@ -62,6 +60,7 @@ class BiometricStatusRepositoryTest : SysuiTestCase() { @Before fun setUp() { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) underTest = BiometricStatusRepositoryImpl(testScope.backgroundScope, biometricManager) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt index 6978923879b4..d7b7d79425c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.biometrics.domain.interactor import android.app.ActivityManager import android.app.ActivityTaskManager import android.content.ComponentName -import android.platform.test.annotations.RequiresFlagsEnabled import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository @@ -44,7 +43,6 @@ import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule -@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR) @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) @@ -59,6 +57,7 @@ class BiometricStatusInteractorImplTest : SysuiTestCase() { @Before fun setup() { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) biometricStatusRepository = FakeBiometricStatusRepository() underTest = BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt index fd5a584935ef..1b6aaabd4fd6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt @@ -22,17 +22,13 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic -import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition -import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModelTransitionsMock +import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.statusbar.phone.systemUIDialogManager import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -48,25 +44,14 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(JUnit4::class) class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { - val kosmos = + private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) } } - val testScope = kosmos.testScope - - private val testDeviceEntryIconTransitionAlpha = MutableStateFlow(0f) - private val testDeviceEntryIconTransition: DeviceEntryIconTransition - get() = - object : DeviceEntryIconTransition { - override val deviceEntryParentViewAlpha: Flow<Float> = - testDeviceEntryIconTransitionAlpha.asStateFlow() - } - - init { - kosmos.deviceEntryIconViewModelTransitionsMock.add(testDeviceEntryIconTransition) - } private val systemUIDialogManager = kosmos.systemUIDialogManager private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository + private val testScope = kosmos.testScope + private val deviceEntryIconViewModelTransition = kosmos.fakeDeviceEntryIconViewModelTransition private val underTest = kosmos.deviceEntryUdfpsTouchOverlayViewModel @Captor @@ -82,7 +67,7 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) - testDeviceEntryIconTransitionAlpha.value = 1f + deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(1f) runCurrent() verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture()) @@ -96,7 +81,7 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) - testDeviceEntryIconTransitionAlpha.value = .3f + deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(.3f) runCurrent() verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture()) @@ -110,7 +95,7 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) - testDeviceEntryIconTransitionAlpha.value = 1f + deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(1f) runCurrent() verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture()) @@ -124,7 +109,7 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) - testDeviceEntryIconTransitionAlpha.value = 0f + deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(0f) runCurrent() bouncerRepository.setAlternateVisible(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt new file mode 100644 index 000000000000..93ce86a2959e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt @@ -0,0 +1,157 @@ +/* + * 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.deviceentry.domain.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository +import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.data.ui.viewmodel.udfpsAccessibilityOverlayViewModel +import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition +import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.data.repository.fakeShadeRepository +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) } + } + private val deviceEntryIconTransition = kosmos.fakeDeviceEntryIconViewModelTransition + private val testScope = kosmos.testScope + private val accessibilityRepository = kosmos.fakeAccessibilityRepository + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository + private val deviceEntryFingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository + private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository + private val shadeRepository = kosmos.fakeShadeRepository + private val underTest = kosmos.udfpsAccessibilityOverlayViewModel + + @Test + fun visible() = + testScope.runTest { + val visible by collectLastValue(underTest.visible) + setupVisibleStateOnLockscreen() + assertThat(visible).isTrue() + } + + @Test + fun touchExplorationNotEnabled_overlayNotVisible() = + testScope.runTest { + val visible by collectLastValue(underTest.visible) + setupVisibleStateOnLockscreen() + accessibilityRepository.isTouchExplorationEnabled.value = false + assertThat(visible).isFalse() + } + + @Test + fun deviceEntryFgIconViewModelAod_overlayNotVisible() = + testScope.runTest { + val visible by collectLastValue(underTest.visible) + setupVisibleStateOnLockscreen() + + // AOD + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ) + ) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ) + ) + assertThat(visible).isFalse() + } + + @Test + fun deviceUnlocked_overlayNotVisible() = + testScope.runTest { + val visible by collectLastValue(underTest.visible) + setupVisibleStateOnLockscreen() + deviceEntryRepository.setUnlocked(true) + assertThat(visible).isFalse() + } + + @Test + fun deviceEntryViewAlpha0_overlayNotVisible() = + testScope.runTest { + val visible by collectLastValue(underTest.visible) + setupVisibleStateOnLockscreen() + deviceEntryIconTransition.setDeviceEntryParentViewAlpha(0f) + assertThat(visible).isFalse() + } + + private suspend fun setupVisibleStateOnLockscreen() { + // A11y enabled + accessibilityRepository.isTouchExplorationEnabled.value = true + + // Transition alpha is 1f + deviceEntryIconTransition.setDeviceEntryParentViewAlpha(1f) + + // Listening for UDFPS + fingerprintPropertyRepository.supportsUdfps() + deviceEntryFingerprintAuthRepository.setIsRunning(true) + deviceEntryRepository.setUnlocked(false) + + // Lockscreen + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 0f, + transitionState = TransitionState.STARTED, + ) + ) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 1f, + transitionState = TransitionState.FINISHED, + ) + ) + + // Shade not expanded + shadeRepository.qsExpansion.value = 0f + shadeRepository.lockscreenShadeExpansion.value = 0f + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt index 70d3f81de35f..0616a3471bbe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.domain.interactor import android.os.Handler -import android.platform.test.annotations.RequiresFlagsEnabled import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor @@ -58,7 +57,6 @@ import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule @OptIn(ExperimentalCoroutinesApi::class) -@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR) @SmallTest @RunWith(JUnit4::class) class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { @@ -80,6 +78,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { @Before fun setup() { + mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) primaryBouncerInteractor = PrimaryBouncerInteractor( bouncerRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 3109e761e423..ad86ee9f07d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -37,6 +37,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopu import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.util.mockito.whenever @@ -70,7 +71,8 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Mock private lateinit var communalTutorialIndicatorSection: CommunalTutorialIndicatorSection @Mock private lateinit var clockSection: ClockSection @Mock private lateinit var smartspaceSection: SmartspaceSection - + @Mock + private lateinit var udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -90,6 +92,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { communalTutorialIndicatorSection, clockSection, smartspaceSection, + udfpsAccessibilityOverlaySection, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt index e89b61f6d44e..dc0d9c5f987e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt @@ -20,7 +20,6 @@ package com.android.systemui.keyguard.ui.view.layout.sections import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel @@ -43,7 +42,6 @@ class ClockSectionTest : SysuiTestCase() { @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel @Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel @Mock private lateinit var splitShadeStateController: SplitShadeStateController - private var featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic() private lateinit var underTest: ClockSection @@ -88,7 +86,6 @@ class ClockSectionTest : SysuiTestCase() { smartspaceViewModel, mContext, splitShadeStateController, - featureFlags ) } @@ -147,24 +144,6 @@ class ClockSectionTest : SysuiTestCase() { assetSmallClockTop(cs, expectedSmallClockTopMargin) } - @Test - fun testLargeClockShouldBeCentered() { - underTest.setClockShouldBeCentered(true) - val cs = ConstraintSet() - underTest.applyDefaultConstraints(cs) - val constraint = cs.getConstraint(R.id.lockscreen_clock_view_large) - assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID) - } - - @Test - fun testLargeClockShouldNotBeCentered() { - underTest.setClockShouldBeCentered(false) - val cs = ConstraintSet() - underTest.applyDefaultConstraints(cs) - val constraint = cs.getConstraint(R.id.lockscreen_clock_view_large) - assertThat(constraint.layout.endToEnd).isEqualTo(R.id.split_shade_guideline) - } - private fun setLargeClock(useLargeClock: Boolean) { whenever(keyguardClockViewModel.useLargeClock).thenReturn(useLargeClock) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt index bff27f6910ab..740110b4fee0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt @@ -69,14 +69,13 @@ class SmartspaceSectionTest : SysuiTestCase() { keyguardUnlockAnimationController, ) constraintLayout = ConstraintLayout(mContext) - whenever(lockscreenSmartspaceController.buildAndConnectView(constraintLayout)) - .thenReturn(smartspaceView) - whenever(lockscreenSmartspaceController.buildAndConnectWeatherView(constraintLayout)) - .thenReturn(weatherView) - whenever(lockscreenSmartspaceController.buildAndConnectDateView(constraintLayout)) - .thenReturn(dateView) + whenever(keyguardSmartspaceViewModel.smartspaceView).thenReturn(smartspaceView) + whenever(keyguardSmartspaceViewModel.weatherView).thenReturn(weatherView) + whenever(keyguardSmartspaceViewModel.dateView).thenReturn(dateView) whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay) .thenReturn(hasCustomWeatherDataDisplay) + whenever(keyguardSmartspaceViewModel.smartspaceController) + .thenReturn(lockscreenSmartspaceController) constraintSet = ConstraintSet() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 459a74c82da4..ee1be10607cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -24,7 +24,6 @@ import androidx.test.filters.SmallTest import com.android.systemui.Flags as AConfigFlags import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION import com.android.systemui.SysuiTestCase -import com.android.systemui.collectLastValue import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.coroutines.collectLastValue diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt deleted file mode 100644 index 6512290bf556..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt +++ /dev/null @@ -1,139 +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.keyguard.ui.viewmodel - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.BurnInInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor -import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.statusbar.phone.SystemUIDialogManager -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations - -@ExperimentalCoroutinesApi -@SmallTest -@RunWith(AndroidJUnit4::class) -class UdfpsAodViewModelTest : SysuiTestCase() { - private val defaultPadding = 12 - private lateinit var underTest: UdfpsAodViewModel - - private lateinit var testScope: TestScope - private lateinit var configRepository: FakeConfigurationRepository - private lateinit var bouncerRepository: KeyguardBouncerRepository - private lateinit var keyguardRepository: FakeKeyguardRepository - private lateinit var shadeRepository: FakeShadeRepository - private lateinit var keyguardInteractor: KeyguardInteractor - - @Mock private lateinit var dialogManager: SystemUIDialogManager - @Mock private lateinit var burnInHelper: BurnInHelperWrapper - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - overrideResource(com.android.systemui.res.R.dimen.lock_icon_padding, defaultPadding) - testScope = TestScope() - shadeRepository = FakeShadeRepository() - KeyguardInteractorFactory.create().also { - keyguardInteractor = it.keyguardInteractor - keyguardRepository = it.repository - configRepository = it.configurationRepository - bouncerRepository = it.bouncerRepository - } - val udfpsKeyguardInteractor = - UdfpsKeyguardInteractor( - configRepository, - BurnInInteractor( - context, - burnInHelper, - testScope.backgroundScope, - configRepository, - keyguardInteractor, - ), - keyguardInteractor, - shadeRepository, - dialogManager, - ) - - underTest = - UdfpsAodViewModel( - udfpsKeyguardInteractor, - context, - ) - } - - @Test - fun alphaAndVisibleUpdates_onDozeAmountChanges() = - testScope.runTest { - val alpha by collectLastValue(underTest.alpha) - val visible by collectLastValue(underTest.isVisible) - - keyguardRepository.setDozeAmount(0f) - runCurrent() - assertThat(alpha).isEqualTo(0f) - assertThat(visible).isFalse() - - keyguardRepository.setDozeAmount(.65f) - runCurrent() - assertThat(alpha).isEqualTo(.65f) - assertThat(visible).isTrue() - - keyguardRepository.setDozeAmount(.23f) - runCurrent() - assertThat(alpha).isEqualTo(.23f) - assertThat(visible).isTrue() - - keyguardRepository.setDozeAmount(1f) - runCurrent() - assertThat(alpha).isEqualTo(1f) - assertThat(visible).isTrue() - } - - @Test - fun paddingUpdates_onScaleForResolutionChanges() = - testScope.runTest { - val padding by collectLastValue(underTest.padding) - - configRepository.setScaleForResolution(1f) - runCurrent() - assertThat(padding).isEqualTo(defaultPadding) - - configRepository.setScaleForResolution(2f) - runCurrent() - assertThat(padding).isEqualTo(defaultPadding * 2) - - configRepository.setScaleForResolution(.5f) - runCurrent() - assertThat(padding).isEqualTo((defaultPadding * .5f).toInt()) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt deleted file mode 100644 index 95b2fe554f0c..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt +++ /dev/null @@ -1,130 +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.keyguard.ui.viewmodel - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository -import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.keyguard.data.repository.FakeCommandQueue -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.BurnInInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory -import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor -import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.statusbar.phone.SystemUIDialogManager -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations - -/** Tests UdfpsFingerprintViewModel specific flows. */ -@ExperimentalCoroutinesApi -@SmallTest -@RunWith(AndroidJUnit4::class) -class UdfpsFingerprintViewModelTest : SysuiTestCase() { - private val defaultPadding = 12 - private lateinit var underTest: FingerprintViewModel - - private lateinit var testScope: TestScope - private lateinit var configRepository: FakeConfigurationRepository - private lateinit var bouncerRepository: KeyguardBouncerRepository - private lateinit var keyguardRepository: FakeKeyguardRepository - private lateinit var fakeCommandQueue: FakeCommandQueue - private lateinit var transitionRepository: FakeKeyguardTransitionRepository - private lateinit var shadeRepository: FakeShadeRepository - - @Mock private lateinit var burnInHelper: BurnInHelperWrapper - @Mock private lateinit var dialogManager: SystemUIDialogManager - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - overrideResource(com.android.systemui.res.R.dimen.lock_icon_padding, defaultPadding) - testScope = TestScope() - configRepository = FakeConfigurationRepository() - keyguardRepository = FakeKeyguardRepository() - bouncerRepository = FakeKeyguardBouncerRepository() - fakeCommandQueue = FakeCommandQueue() - bouncerRepository = FakeKeyguardBouncerRepository() - transitionRepository = FakeKeyguardTransitionRepository() - shadeRepository = FakeShadeRepository() - val keyguardInteractor = - KeyguardInteractorFactory.create( - repository = keyguardRepository, - ) - .keyguardInteractor - - val transitionInteractor = - KeyguardTransitionInteractorFactory.create( - scope = testScope.backgroundScope, - repository = transitionRepository, - keyguardInteractor = keyguardInteractor, - ) - .keyguardTransitionInteractor - - underTest = - FingerprintViewModel( - context, - transitionInteractor, - UdfpsKeyguardInteractor( - configRepository, - BurnInInteractor( - context, - burnInHelper, - testScope.backgroundScope, - configRepository, - keyguardInteractor, - ), - keyguardInteractor, - shadeRepository, - dialogManager, - ), - keyguardInteractor, - ) - } - - @Test - fun paddingUpdates_onScaleForResolutionChanges() = - testScope.runTest { - val padding by collectLastValue(underTest.padding) - - configRepository.setScaleForResolution(1f) - runCurrent() - assertThat(padding).isEqualTo(defaultPadding) - - configRepository.setScaleForResolution(2f) - runCurrent() - assertThat(padding).isEqualTo(defaultPadding * 2) - - configRepository.setScaleForResolution(.5f) - runCurrent() - assertThat(padding).isEqualTo((defaultPadding * .5).toInt()) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt deleted file mode 100644 index 848a94b2a5d2..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt +++ /dev/null @@ -1,749 +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.keyguard.ui.viewmodel - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.settingslib.Utils -import com.android.systemui.SysuiTestCase -import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.BurnInInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory -import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.StatusBarState -import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.statusbar.phone.SystemUIDialogManager -import com.android.systemui.util.mockito.argumentCaptor -import com.android.systemui.util.mockito.mock -import com.android.wm.shell.animation.Interpolators -import com.google.common.collect.Range -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.MockitoAnnotations - -/** Tests UDFPS lockscreen view model transitions. */ -@ExperimentalCoroutinesApi -@SmallTest -@RunWith(AndroidJUnit4::class) -class UdfpsLockscreenViewModelTest : SysuiTestCase() { - private val lockscreenColorResId = android.R.attr.textColorPrimary - private val alternateBouncerResId = com.android.internal.R.attr.materialColorOnPrimaryFixed - private val lockscreenColor = Utils.getColorAttrDefaultColor(context, lockscreenColorResId) - private val alternateBouncerColor = - Utils.getColorAttrDefaultColor(context, alternateBouncerResId) - - @Mock private lateinit var dialogManager: SystemUIDialogManager - - private lateinit var underTest: UdfpsLockscreenViewModel - private lateinit var testScope: TestScope - private lateinit var transitionRepository: FakeKeyguardTransitionRepository - private lateinit var configRepository: FakeConfigurationRepository - private lateinit var keyguardRepository: FakeKeyguardRepository - private lateinit var keyguardInteractor: KeyguardInteractor - private lateinit var bouncerRepository: FakeKeyguardBouncerRepository - private lateinit var shadeRepository: FakeShadeRepository - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - testScope = TestScope() - transitionRepository = FakeKeyguardTransitionRepository() - shadeRepository = FakeShadeRepository() - KeyguardInteractorFactory.create().also { - keyguardInteractor = it.keyguardInteractor - keyguardRepository = it.repository - configRepository = it.configurationRepository - bouncerRepository = it.bouncerRepository - } - - val transitionInteractor = - KeyguardTransitionInteractorFactory.create( - scope = testScope.backgroundScope, - repository = transitionRepository, - keyguardInteractor = keyguardInteractor, - ) - .keyguardTransitionInteractor - - underTest = - UdfpsLockscreenViewModel( - context, - lockscreenColorResId, - alternateBouncerResId, - transitionInteractor, - UdfpsKeyguardInteractor( - configRepository, - BurnInInteractor( - context, - burnInHelperWrapper = mock(), - testScope.backgroundScope, - configRepository, - keyguardInteractor, - ), - keyguardInteractor, - shadeRepository, - dialogManager, - ), - keyguardInteractor, - ) - } - - @Test - fun goneToAodTransition() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - val visible by collectLastValue(underTest.visible) - - // TransitionState.STARTED: gone -> AOD - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 0f, - transitionState = TransitionState.STARTED, - ownerName = "goneToAodTransition", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(visible).isFalse() - - // TransitionState.RUNNING: gone -> AOD - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = .6f, - transitionState = TransitionState.RUNNING, - ownerName = "goneToAodTransition", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(visible).isFalse() - - // TransitionState.FINISHED: gone -> AOD - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED, - ownerName = "goneToAodTransition", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(visible).isFalse() - } - - @Test - fun lockscreenToAod() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - val visible by collectLastValue(underTest.visible) - keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - - // TransitionState.STARTED: lockscreen -> AOD - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = 0f, - transitionState = TransitionState.STARTED, - ownerName = "lockscreenToAod", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(lockscreenColor) - assertThat(visible).isTrue() - - // TransitionState.RUNNING: lockscreen -> AOD - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = .6f, - transitionState = TransitionState.RUNNING, - ownerName = "lockscreenToAod", - ) - ) - runCurrent() - assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f)) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(lockscreenColor) - assertThat(visible).isTrue() - - // TransitionState.FINISHED: lockscreen -> AOD - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED, - ownerName = "lockscreenToAod", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(lockscreenColor) - assertThat(visible).isFalse() - } - - @Test - fun lockscreenShadeLockedToAod() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - val visible by collectLastValue(underTest.visible) - keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) - - // TransitionState.STARTED: lockscreen -> AOD - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = 0f, - transitionState = TransitionState.STARTED, - ownerName = "lockscreenToAod", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(visible).isFalse() - - // TransitionState.RUNNING: lockscreen -> AOD - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = .6f, - transitionState = TransitionState.RUNNING, - ownerName = "lockscreenToAod", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(visible).isFalse() - - // TransitionState.FINISHED: lockscreen -> AOD - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED, - ownerName = "lockscreenToAod", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(visible).isFalse() - } - - @Test - fun aodToLockscreen() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - val visible by collectLastValue(underTest.visible) - - // TransitionState.STARTED: AOD -> lockscreen - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, - value = 0f, - transitionState = TransitionState.STARTED, - ownerName = "aodToLockscreen", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(lockscreenColor) - assertThat(visible).isFalse() - - // TransitionState.RUNNING: AOD -> lockscreen - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, - value = .6f, - transitionState = TransitionState.RUNNING, - ownerName = "aodToLockscreen", - ) - ) - runCurrent() - assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f)) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(lockscreenColor) - assertThat(visible).isTrue() - - // TransitionState.FINISHED: AOD -> lockscreen - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, - value = 1f, - transitionState = TransitionState.FINISHED, - ownerName = "aodToLockscreen", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(lockscreenColor) - assertThat(visible).isTrue() - } - - @Test - fun lockscreenToAlternateBouncer() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - val visible by collectLastValue(underTest.visible) - keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - - // TransitionState.STARTED: lockscreen -> alternate bouncer - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.ALTERNATE_BOUNCER, - value = 0f, - transitionState = TransitionState.STARTED, - ownerName = "lockscreenToAlternateBouncer", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(alternateBouncerColor) - assertThat(visible).isTrue() - - // TransitionState.RUNNING: lockscreen -> alternate bouncer - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.ALTERNATE_BOUNCER, - value = .6f, - transitionState = TransitionState.RUNNING, - ownerName = "lockscreenToAlternateBouncer", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(alternateBouncerColor) - assertThat(visible).isTrue() - - // TransitionState.FINISHED: lockscreen -> alternate bouncer - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.ALTERNATE_BOUNCER, - value = 1f, - transitionState = TransitionState.FINISHED, - ownerName = "lockscreenToAlternateBouncer", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(alternateBouncerColor) - assertThat(visible).isTrue() - } - - fun alternateBouncerToPrimaryBouncer() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - val visible by collectLastValue(underTest.visible) - - // TransitionState.STARTED: alternate bouncer -> primary bouncer - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.PRIMARY_BOUNCER, - value = 0f, - transitionState = TransitionState.STARTED, - ownerName = "alternateBouncerToPrimaryBouncer", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(alternateBouncerColor) - assertThat(visible).isTrue() - - // TransitionState.RUNNING: alternate bouncer -> primary bouncer - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.PRIMARY_BOUNCER, - value = .6f, - transitionState = TransitionState.RUNNING, - ownerName = "alternateBouncerToPrimaryBouncer", - ) - ) - runCurrent() - assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f)) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(alternateBouncerColor) - assertThat(visible).isTrue() - - // TransitionState.FINISHED: alternate bouncer -> primary bouncer - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.PRIMARY_BOUNCER, - value = 1f, - transitionState = TransitionState.FINISHED, - ownerName = "alternateBouncerToPrimaryBouncer", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(alternateBouncerColor) - assertThat(visible).isFalse() - } - - fun alternateBouncerToAod() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - val visible by collectLastValue(underTest.visible) - - // TransitionState.STARTED: alternate bouncer -> aod - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.AOD, - value = 0f, - transitionState = TransitionState.STARTED, - ownerName = "alternateBouncerToAod", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(alternateBouncerColor) - assertThat(visible).isTrue() - - // TransitionState.RUNNING: alternate bouncer -> aod - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.AOD, - value = .6f, - transitionState = TransitionState.RUNNING, - ownerName = "alternateBouncerToAod", - ) - ) - runCurrent() - assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f)) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(alternateBouncerColor) - assertThat(visible).isTrue() - - // TransitionState.FINISHED: alternate bouncer -> aod - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED, - ownerName = "alternateBouncerToAod", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(alternateBouncerColor) - assertThat(visible).isFalse() - } - - @Test - fun lockscreenToOccluded() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - val visible by collectLastValue(underTest.visible) - keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - - // TransitionState.STARTED: lockscreen -> occluded - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.OCCLUDED, - value = 0f, - transitionState = TransitionState.STARTED, - ownerName = "lockscreenToOccluded", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(lockscreenColor) - assertThat(visible).isTrue() - - // TransitionState.RUNNING: lockscreen -> occluded - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.OCCLUDED, - value = .6f, - transitionState = TransitionState.RUNNING, - ownerName = "lockscreenToOccluded", - ) - ) - runCurrent() - assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f)) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(lockscreenColor) - assertThat(visible).isTrue() - - // TransitionState.FINISHED: lockscreen -> occluded - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.OCCLUDED, - value = 1f, - transitionState = TransitionState.FINISHED, - ownerName = "lockscreenToOccluded", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(lockscreenColor) - assertThat(visible).isFalse() - } - - @Test - fun occludedToLockscreen() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - val visible by collectLastValue(underTest.visible) - - // TransitionState.STARTED: occluded -> lockscreen - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.LOCKSCREEN, - value = 0f, - transitionState = TransitionState.STARTED, - ownerName = "occludedToLockscreen", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(lockscreenColor) - assertThat(visible).isTrue() - - // TransitionState.RUNNING: occluded -> lockscreen - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.LOCKSCREEN, - value = .6f, - transitionState = TransitionState.RUNNING, - ownerName = "occludedToLockscreen", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(lockscreenColor) - assertThat(visible).isTrue() - - // TransitionState.FINISHED: occluded -> lockscreen - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.LOCKSCREEN, - value = 1f, - transitionState = TransitionState.FINISHED, - ownerName = "occludedToLockscreen", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(lockscreenColor) - assertThat(visible).isTrue() - } - - @Test - fun qsProgressChange() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - val visible by collectLastValue(underTest.visible) - givenTransitionToLockscreenFinished() - - // qsExpansion = 0f - shadeRepository.setQsExpansion(0f) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(visible).isEqualTo(true) - - // qsExpansion = .25 - shadeRepository.setQsExpansion(.2f) - runCurrent() - assertThat(transition?.alpha).isEqualTo(.6f) - assertThat(visible).isEqualTo(true) - - // qsExpansion = .5 - shadeRepository.setQsExpansion(.5f) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(visible).isEqualTo(false) - - // qsExpansion = 1 - shadeRepository.setQsExpansion(1f) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(visible).isEqualTo(false) - } - - @Test - fun shadeExpansionChanged() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - val visible by collectLastValue(underTest.visible) - givenTransitionToLockscreenFinished() - - // shadeExpansion = 0f - shadeRepository.setUdfpsTransitionToFullShadeProgress(0f) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(visible).isEqualTo(true) - - // shadeExpansion = .2 - shadeRepository.setUdfpsTransitionToFullShadeProgress(.2f) - runCurrent() - assertThat(transition?.alpha).isEqualTo(.8f) - assertThat(visible).isEqualTo(true) - - // shadeExpansion = .5 - shadeRepository.setUdfpsTransitionToFullShadeProgress(.5f) - runCurrent() - assertThat(transition?.alpha).isEqualTo(.5f) - assertThat(visible).isEqualTo(true) - - // shadeExpansion = 1 - shadeRepository.setUdfpsTransitionToFullShadeProgress(1f) - runCurrent() - assertThat(transition?.alpha).isEqualTo(0f) - assertThat(visible).isEqualTo(false) - } - - @Test - fun dialogHideAffordancesRequestChanged() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - givenTransitionToLockscreenFinished() - runCurrent() - val captor = argumentCaptor<SystemUIDialogManager.Listener>() - Mockito.verify(dialogManager).registerListener(captor.capture()) - - captor.value.shouldHideAffordances(true) - assertThat(transition?.alpha).isEqualTo(0f) - - captor.value.shouldHideAffordances(false) - assertThat(transition?.alpha).isEqualTo(1f) - } - - @Test - fun occludedToAlternateBouncer() = - testScope.runTest { - val transition by collectLastValue(underTest.transition) - val visible by collectLastValue(underTest.visible) - - // TransitionState.STARTED: occluded -> alternate bouncer - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.ALTERNATE_BOUNCER, - value = 0f, - transitionState = TransitionState.STARTED, - ownerName = "occludedToAlternateBouncer", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(0f) - assertThat(transition?.color).isEqualTo(alternateBouncerColor) - assertThat(visible).isTrue() - - // TransitionState.RUNNING: occluded -> alternate bouncer - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.ALTERNATE_BOUNCER, - value = .6f, - transitionState = TransitionState.RUNNING, - ownerName = "occludedToAlternateBouncer", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale) - .isEqualTo(Interpolators.FAST_OUT_SLOW_IN.getInterpolation(.6f)) - assertThat(transition?.color).isEqualTo(alternateBouncerColor) - assertThat(visible).isTrue() - - // TransitionState.FINISHED: occluded -> alternate bouncer - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.ALTERNATE_BOUNCER, - value = 1f, - transitionState = TransitionState.FINISHED, - ownerName = "occludedToAlternateBouncer", - ) - ) - runCurrent() - assertThat(transition?.alpha).isEqualTo(1f) - assertThat(transition?.scale).isEqualTo(1f) - assertThat(transition?.color).isEqualTo(alternateBouncerColor) - assertThat(visible).isTrue() - } - - private suspend fun givenTransitionToLockscreenFinished() { - transitionRepository.sendTransitionSteps( - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, - testScope - ) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index 4d423242893a..b7618d290f53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -75,7 +75,6 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt @@ -463,7 +462,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() { // region setNoteTaskShortcutEnabled @Test - @Ignore("b/316332684") fun setNoteTaskShortcutEnabled_setTrue() { createNoteTaskController().setNoteTaskShortcutEnabled(value = true, userTracker.userHandle) @@ -480,7 +478,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - @Ignore("b/316332684") fun setNoteTaskShortcutEnabled_setFalse() { createNoteTaskController().setNoteTaskShortcutEnabled(value = false, userTracker.userHandle) @@ -497,7 +494,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - @Ignore("b/316332684") fun setNoteTaskShortcutEnabled_workProfileUser_setTrue() { whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any())) .thenReturn(workProfileContext) @@ -519,7 +515,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - @Ignore("b/316332684") fun setNoteTaskShortcutEnabled_workProfileUser_setFalse() { whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any())) .thenReturn(workProfileContext) @@ -738,7 +733,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() { // region internalUpdateNoteTaskAsUser @Test - @Ignore("b/316332684") fun updateNoteTaskAsUserInternal_withNotesRole_withShortcuts_shouldUpdateShortcuts() { createNoteTaskController(isEnabled = true) .launchUpdateNoteTaskAsUser(userTracker.userHandle) @@ -772,7 +766,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - @Ignore("b/316332684") fun updateNoteTaskAsUserInternal_noNotesRole_shouldDisableShortcuts() { whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle)) .thenReturn(emptyList()) @@ -796,7 +789,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - @Ignore("b/316332684") fun updateNoteTaskAsUserInternal_flagDisabled_shouldDisableShortcuts() { createNoteTaskController(isEnabled = false) .launchUpdateNoteTaskAsUser(userTracker.userHandle) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 5e2423a8b373..ef7798e545d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -447,10 +447,6 @@ public class QSTileHostTest extends SysuiTestCase { mContext.getOrCreateTestableResources() .addOverride(R.string.quick_settings_tiles_default, "spec1,spec1"); List<String> specs = QSTileHost.loadTileSpecs(mContext, "default"); - - // Remove spurious tiles, like dbg:mem - specs.removeIf(spec -> !"spec1".equals(spec)); - assertEquals(1, specs.size()); } @Test @@ -458,10 +454,6 @@ public class QSTileHostTest extends SysuiTestCase { mContext.getOrCreateTestableResources() .addOverride(R.string.quick_settings_tiles_default, "spec1"); List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1"); - - // Remove spurious tiles, like dbg:mem - specs.removeIf(spec -> !"spec1".equals(spec)); - assertEquals(1, specs.size()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt index 067218a9c983..5201e5d9ccf7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt @@ -50,7 +50,6 @@ import com.android.systemui.qs.tiles.RotationLockTile import com.android.systemui.qs.tiles.ScreenRecordTile import com.android.systemui.qs.tiles.UiModeNightTile import com.android.systemui.qs.tiles.WorkModeTile -import com.android.systemui.util.leak.GarbageMonitor import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -117,7 +116,6 @@ class QSFactoryImplTest : SysuiTestCase() { @Mock private lateinit var dataSaverTile: DataSaverTile @Mock private lateinit var nightDisplayTile: NightDisplayTile @Mock private lateinit var nfcTile: NfcTile - @Mock private lateinit var memoryTile: GarbageMonitor.MemoryTile @Mock private lateinit var darkModeTile: UiModeNightTile @Mock private lateinit var screenRecordTile: ScreenRecordTile @Mock private lateinit var reduceBrightColorsTile: ReduceBrightColorsTile diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt index d4e8d37b3b44..72fc65b7c8d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt @@ -10,8 +10,6 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.Guideline import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever @@ -39,7 +37,6 @@ class MessageContainerControllerTest : SysuiTestCase() { lateinit var detectionNoticeView: ViewGroup lateinit var container: FrameLayout - var featureFlags = FakeFeatureFlags() lateinit var screenshotView: ViewGroup val userHandle = UserHandle.of(5) @@ -55,7 +52,6 @@ class MessageContainerControllerTest : SysuiTestCase() { MessageContainerController( workProfileMessageController, screenshotDetectionController, - featureFlags ) screenshotView = ConstraintLayout(mContext) workProfileData = WorkProfileMessageController.WorkProfileFirstRunData(appName, icon) @@ -105,8 +101,6 @@ class MessageContainerControllerTest : SysuiTestCase() { @Test fun testOnScreenshotTakenScreenshotData_nothingToShow() { - featureFlags.set(Flags.SCREENSHOT_DETECTION, true) - messageContainer.onScreenshotTaken(screenshotData) verify(workProfileMessageController, never()).populateView(any(), any(), any()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt new file mode 100644 index 000000000000..80f8cf1559a6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt @@ -0,0 +1,85 @@ +/* + * 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.shared.notifications.data.repository + +import android.provider.Settings +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.shared.settings.data.repository.FakeSecureSettingsRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class NotificationSettingsRepositoryTest : SysuiTestCase() { + + private lateinit var underTest: NotificationSettingsRepository + + private lateinit var testScope: TestScope + private lateinit var secureSettingsRepository: FakeSecureSettingsRepository + + @Before + fun setUp() { + val testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) + secureSettingsRepository = FakeSecureSettingsRepository() + + underTest = + NotificationSettingsRepository( + scope = testScope.backgroundScope, + backgroundDispatcher = testDispatcher, + secureSettingsRepository = secureSettingsRepository, + ) + } + + @Test + fun testGetIsShowNotificationsOnLockscreenEnabled() = + testScope.runTest { + val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled) + + secureSettingsRepository.set( + name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, + value = 1, + ) + assertThat(showNotifs).isEqualTo(true) + + secureSettingsRepository.set( + name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, + value = 0, + ) + assertThat(showNotifs).isEqualTo(false) + } + + @Test + fun testSetIsShowNotificationsOnLockscreenEnabled() = + testScope.runTest { + val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled) + + underTest.setShowNotificationsOnLockscreenEnabled(true) + assertThat(showNotifs).isEqualTo(true) + + underTest.setShowNotificationsOnLockscreenEnabled(false) + assertThat(showNotifs).isEqualTo(false) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt index a56fb2c515a8..7d8cf3657ba1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt @@ -1,11 +1,10 @@ package com.android.systemui.statusbar.notification +import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import org.junit.Assert.assertEquals @@ -20,8 +19,7 @@ import org.mockito.Mockito.verify @RunWith(JUnit4::class) class RoundableTest : SysuiTestCase() { private val targetView: View = mock() - private val featureFlags = FakeFeatureFlags() - private val roundable = FakeRoundable(targetView = targetView, featureFlags = featureFlags) + private val roundable = FakeRoundable(targetView = targetView) @Test fun defaultConfig_shouldNotHaveRoundedCorner() { @@ -150,36 +148,36 @@ class RoundableTest : SysuiTestCase() { } @Test + @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun getCornerRadii_radius_maxed_to_height() { whenever(targetView.height).thenReturn(10) - featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true) roundable.requestRoundness(1f, 1f, SOURCE1) assertCornerRadiiEquals(5f, 5f) } @Test + @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun getCornerRadii_topRadius_maxed_to_height() { whenever(targetView.height).thenReturn(5) - featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true) roundable.requestRoundness(1f, 0f, SOURCE1) assertCornerRadiiEquals(5f, 0f) } @Test + @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun getCornerRadii_bottomRadius_maxed_to_height() { whenever(targetView.height).thenReturn(5) - featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true) roundable.requestRoundness(0f, 1f, SOURCE1) assertCornerRadiiEquals(0f, 5f) } @Test + @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun getCornerRadii_radii_kept() { whenever(targetView.height).thenReturn(100) - featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true) roundable.requestRoundness(1f, 1f, SOURCE1) assertCornerRadiiEquals(MAX_RADIUS, MAX_RADIUS) @@ -193,14 +191,12 @@ class RoundableTest : SysuiTestCase() { class FakeRoundable( targetView: View, radius: Float = MAX_RADIUS, - featureFlags: FeatureFlags ) : Roundable { override val roundableState = RoundableState( targetView = targetView, roundable = this, maxRadius = radius, - featureFlags = featureFlags ) override val clipHeight: Int diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt index 360a373711d6..47feccf4bdcf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.data.model.activeNotification import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository +import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor import com.android.systemui.statusbar.notification.shared.byIsAmbient import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply import com.android.systemui.statusbar.notification.shared.byIsPulsing @@ -264,6 +265,7 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() { interface TestComponent : SysUITestComponent<StatusBarNotificationIconsInteractor> { val activeNotificationListRepository: ActiveNotificationListRepository + val headsUpIconsInteractor: HeadsUpNotificationIconInteractor val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository val notificationListenerSettingsRepository: NotificationListenerSettingsRepository @@ -336,6 +338,14 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() { .comparingElementsUsing(byIsLastMessageFromReply) .doesNotContain(true) } + + @Test + fun filteredEntrySet_includesIsolatedIcon() = + testComponent.runTest { + val filteredSet by collectLastValue(underTest.statusBarNotifs) + headsUpIconsInteractor.setIsolatedIconNotificationKey("notif5") + assertThat(filteredSet).comparingElementsUsing(byKey).contains("notif5") + } } private val testIcons = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt index 349a35ebf798..c40401fe4d59 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt @@ -399,4 +399,29 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1") } + + @Test + fun isolatedIcon_lastMessageIsFromReply_notNull() = + testComponent.runTest { + val icon: Icon = mock() + headsUpViewStateRepository.isolatedNotification.value = "notif1" + activeNotificationsRepository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", + groupKey = "group", + statusBarIcon = icon, + isLastMessageFromReply = true, + ) + ) + } + .build() + + val isolatedIcon by collectLastValue(underTest.isolatedIcon) + runCurrent() + + assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1") + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index cb731082b89b..0cd834ded638 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -57,7 +57,6 @@ import com.android.systemui.TestableDependency; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.media.controls.util.MediaFeatureFlag; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -568,9 +567,6 @@ public class NotificationTestHelper { NotificationEntry entry, @InflationFlag int extraInflationFlags) throws Exception { - // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be - // set, but we do not want to override an existing value that is needed by a specific test. - mFeatureFlags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS); LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( mContext.LAYOUT_INFLATER_SERVICE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index c8dbdc505aba..2df6e46d630f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -28,8 +28,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.mock -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever /** Tests for {@link NotificationShelf}. */ @SmallTest @@ -53,7 +53,6 @@ open class NotificationShelfTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) mDependency.injectTestDependency(FeatureFlags::class.java, flags) flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal) - flags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS) val root = FrameLayout(context) shelf = LayoutInflater.from(root.context) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 08ef47765174..f266f039958f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -2,21 +2,28 @@ package com.android.systemui.statusbar.notification.stack import android.annotation.DimenRes import android.content.pm.PackageManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation.getContentAlpha import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.res.R import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.EmptyShadeView import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.notification.RoundableState +import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.statusbar.notification.footer.ui.view.FooterView.FooterViewState import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.util.mockito.mock import com.google.common.truth.Expect @@ -24,6 +31,7 @@ import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Assume import org.junit.Before import org.junit.Rule @@ -37,22 +45,26 @@ import org.mockito.Mockito.`when` as whenever @SmallTest class StackScrollAlgorithmTest : SysuiTestCase() { - @JvmField @Rule - var expect: Expect = Expect.create() + @JvmField @Rule var expect: Expect = Expect.create() private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>() private val hostView = FrameLayout(context) private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView) private val notificationRow = mock<ExpandableNotificationRow>() + private val notificationEntry = mock<NotificationEntry>() private val dumpManager = mock<DumpManager>() + @OptIn(ExperimentalCoroutinesApi::class) private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>() private val notificationShelf = mock<NotificationShelf>() - private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply { - layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100) - } - private val footerView = FooterView(context, /*attrs=*/null) - private val ambientState = AmbientState( + private val emptyShadeView = + EmptyShadeView(context, /* attrs= */ null).apply { + layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100) + } + private val footerView = FooterView(context, /*attrs=*/ null) + @OptIn(ExperimentalCoroutinesApi::class) + private val ambientState = + AmbientState( context, dumpManager, /* sectionProvider */ { _, _ -> false }, @@ -62,13 +74,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() { ) private val testableResources = mContext.getOrCreateTestableResources() + private val featureFlags = mock<FeatureFlagsClassic>() private val maxPanelHeight = mContext.resources.displayMetrics.heightPixels - - px(R.dimen.notification_panel_margin_top) - - px(R.dimen.notification_panel_margin_bottom) + px(R.dimen.notification_panel_margin_top) - + px(R.dimen.notification_panel_margin_bottom) private fun px(@DimenRes id: Int): Float = - testableResources.resources.getDimensionPixelSize(id).toFloat() + testableResources.resources.getDimensionPixelSize(id).toFloat() private val bigGap = px(R.dimen.notification_section_divider_height) private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen) @@ -76,9 +89,12 @@ class StackScrollAlgorithmTest : SysuiTestCase() { @Before fun setUp() { Assume.assumeFalse(isTv()) - + mDependency.injectTestDependency(FeatureFlags::class.java, featureFlags) whenever(notificationShelf.viewState).thenReturn(ExpandableViewState()) whenever(notificationRow.viewState).thenReturn(ExpandableViewState()) + whenever(notificationRow.entry).thenReturn(notificationEntry) + whenever(notificationRow.roundableState) + .thenReturn(RoundableState(notificationRow, notificationRow, 0f)) ambientState.isSmallScreen = true hostView.addView(notificationRow) @@ -92,7 +108,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { fun resetViewStates_defaultHun_yTranslationIsInset() { whenever(notificationRow.isPinned).thenReturn(true) whenever(notificationRow.isHeadsUp).thenReturn(true) - resetViewStates_hunYTranslationIsInset() + resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset) } @Test @@ -103,18 +119,87 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_hunAnimatingAway_yTranslationIsInset() { whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true) - resetViewStates_hunYTranslationIsInset() + resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset) } @Test + @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_hunAnimatingAway_StackMarginChangesHunYTranslation() { whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true) resetViewStates_stackMargin_changesHunYTranslation() } @Test + @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) + fun resetViewStates_defaultHun_newHeadsUpAnim_yTranslationIsInset() { + whenever(notificationRow.isPinned).thenReturn(true) + whenever(notificationRow.isHeadsUp).thenReturn(true) + resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset) + } + + @Test + @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) + fun resetViewStates_defaultHunWithStackMargin_newHeadsUpAnim_changesHunYTranslation() { + whenever(notificationRow.isPinned).thenReturn(true) + whenever(notificationRow.isHeadsUp).thenReturn(true) + resetViewStates_stackMargin_changesHunYTranslation() + } + + @Test + @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) + fun resetViewStates_defaultHun_showingQS_newHeadsUpAnim_hunTranslatedToMax() { + // Given: the shade is open and scrolled to the bottom to show the QuickSettings + val maxHunTranslation = 2000f + ambientState.maxHeadsUpTranslation = maxHunTranslation + ambientState.setLayoutMinHeight(2500) // Mock the height of shade + ambientState.stackY = 2500f // Scroll over the max translation + stackScrollAlgorithm.setIsExpanded(true) // Mark the shade open + whenever(notificationRow.mustStayOnScreen()).thenReturn(true) + whenever(notificationRow.isHeadsUp).thenReturn(true) + whenever(notificationRow.isAboveShelf).thenReturn(true) + + resetViewStates_hunYTranslationIs(maxHunTranslation) + } + + @Test + @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) + fun resetViewStates_hunAnimatingAway_showingQS_newHeadsUpAnim_hunTranslatedToBottomOfScreen() { + // Given: the shade is open and scrolled to the bottom to show the QuickSettings + val bottomOfScreen = 2600f + val maxHunTranslation = 2000f + ambientState.maxHeadsUpTranslation = maxHunTranslation + ambientState.setLayoutMinHeight(2500) // Mock the height of shade + ambientState.stackY = 2500f // Scroll over the max translation + stackScrollAlgorithm.setIsExpanded(true) // Mark the shade open + stackScrollAlgorithm.setHeadsUpAppearHeightBottom(bottomOfScreen.toInt()) + whenever(notificationRow.mustStayOnScreen()).thenReturn(true) + whenever(notificationRow.isHeadsUp).thenReturn(true) + whenever(notificationRow.isAboveShelf).thenReturn(true) + whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true) + + resetViewStates_hunYTranslationIs( + expected = bottomOfScreen + stackScrollAlgorithm.mHeadsUpAppearStartAboveScreen + ) + } + + @Test + @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) + fun resetViewStates_hunAnimatingAway_newHeadsUpAnim_hunTranslatedToTopOfScreen() { + val topMargin = 100f + ambientState.maxHeadsUpTranslation = 2000f + ambientState.stackTopMargin = topMargin.toInt() + whenever(notificationRow.intrinsicHeight).thenReturn(100) + whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true) + + resetViewStates_hunYTranslationIs( + expected = -topMargin - stackScrollAlgorithm.mHeadsUpAppearStartAboveScreen + ) + } + + @Test fun resetViewStates_hunAnimatingAway_bottomNotClipped() { whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true) @@ -136,6 +221,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_hunsOverlappingAndBottomHunAnimatingAway_bottomHunClipped() { val topHun = mockExpandableNotificationRow() val bottomHun = mockExpandableNotificationRow() @@ -156,7 +242,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) val marginBottom = - context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom) + context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom) val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f assertThat(emptyShadeView.viewState.yTranslation).isEqualTo(centeredY) @@ -174,33 +260,37 @@ class StackScrollAlgorithmTest : SysuiTestCase() { assertThat(notificationRow.viewState.alpha).isEqualTo(1f) } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun resetViewStates_expansionChanging_notificationBecomesTransparent() { whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false) resetViewStates_expansionChanging_notificationAlphaUpdated( - expansionFraction = 0.25f, - expectedAlpha = 0.0f + expansionFraction = 0.25f, + expectedAlpha = 0.0f ) } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun resetViewStates_expansionChangingWhileBouncerInTransit_viewBecomesTransparent() { whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true) resetViewStates_expansionChanging_notificationAlphaUpdated( - expansionFraction = 0.85f, - expectedAlpha = 0.0f + expansionFraction = 0.85f, + expectedAlpha = 0.0f ) } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun resetViewStates_expansionChanging_notificationAlphaUpdated() { whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false) resetViewStates_expansionChanging_notificationAlphaUpdated( - expansionFraction = 0.6f, - expectedAlpha = getContentAlpha(0.6f) + expansionFraction = 0.6f, + expectedAlpha = getContentAlpha(0.6f) ) } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun resetViewStates_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() { val expansionFraction = 0.6f @@ -216,13 +306,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() { ) } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun expansionChanging_largeScreen_bouncerInTransit_alphaUpdated_bouncerValues() { ambientState.isSmallScreen = false whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true) resetViewStates_expansionChanging_notificationAlphaUpdated( - expansionFraction = 0.95f, - expectedAlpha = aboutToShowBouncerProgress(0.95f), + expansionFraction = 0.95f, + expectedAlpha = aboutToShowBouncerProgress(0.95f), ) } @@ -235,10 +326,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() { stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) - verify(notificationShelf).updateState( - /* algorithmState= */any(), - /* ambientState= */eq(ambientState) - ) + verify(notificationShelf) + .updateState(/* algorithmState= */ any(), /* ambientState= */ eq(ambientState)) } @Test @@ -397,22 +486,31 @@ class StackScrollAlgorithmTest : SysuiTestCase() { @Test fun getGapForLocation_onLockscreen_returnsSmallGap() { - val gap = stackScrollAlgorithm.getGapForLocation( - /* fractionToShade= */ 0f, /* onKeyguard= */ true) + val gap = + stackScrollAlgorithm.getGapForLocation( + /* fractionToShade= */ 0f, + /* onKeyguard= */ true + ) assertThat(gap).isEqualTo(smallGap) } @Test fun getGapForLocation_goingToShade_interpolatesGap() { - val gap = stackScrollAlgorithm.getGapForLocation( - /* fractionToShade= */ 0.5f, /* onKeyguard= */ true) + val gap = + stackScrollAlgorithm.getGapForLocation( + /* fractionToShade= */ 0.5f, + /* onKeyguard= */ true + ) assertThat(gap).isEqualTo(smallGap * 0.5f + bigGap * 0.5f) } @Test fun getGapForLocation_notOnLockscreen_returnsBigGap() { - val gap = stackScrollAlgorithm.getGapForLocation( - /* fractionToShade= */ 0f, /* onKeyguard= */ false) + val gap = + stackScrollAlgorithm.getGapForLocation( + /* fractionToShade= */ 0f, + /* onKeyguard= */ false + ) assertThat(gap).isEqualTo(bigGap) } @@ -469,12 +567,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() { val expandableViewState = ExpandableViewState() expandableViewState.headsUpIsVisible = false - stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState, - /* isShadeExpanded= */ true, - /* mustStayOnScreen= */ true, - /* isViewEndVisible= */ true, - /* viewEnd= */ 0f, - /* maxHunY= */ 10f) + stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible( + expandableViewState, + /* isShadeExpanded= */ true, + /* mustStayOnScreen= */ true, + /* isViewEndVisible= */ true, + /* viewEnd= */ 0f, + /* maxHunY= */ 10f + ) assertTrue(expandableViewState.headsUpIsVisible) } @@ -484,12 +584,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() { val expandableViewState = ExpandableViewState() expandableViewState.headsUpIsVisible = true - stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState, - /* isShadeExpanded= */ true, - /* mustStayOnScreen= */ true, - /* isViewEndVisible= */ true, - /* viewEnd= */ 10f, - /* maxHunY= */ 0f) + stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible( + expandableViewState, + /* isShadeExpanded= */ true, + /* mustStayOnScreen= */ true, + /* isViewEndVisible= */ true, + /* viewEnd= */ 10f, + /* maxHunY= */ 0f + ) assertFalse(expandableViewState.headsUpIsVisible) } @@ -499,12 +601,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() { val expandableViewState = ExpandableViewState() expandableViewState.headsUpIsVisible = true - stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState, - /* isShadeExpanded= */ false, - /* mustStayOnScreen= */ true, - /* isViewEndVisible= */ true, - /* viewEnd= */ 10f, - /* maxHunY= */ 1f) + stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible( + expandableViewState, + /* isShadeExpanded= */ false, + /* mustStayOnScreen= */ true, + /* isViewEndVisible= */ true, + /* viewEnd= */ 10f, + /* maxHunY= */ 1f + ) assertTrue(expandableViewState.headsUpIsVisible) } @@ -514,12 +618,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() { val expandableViewState = ExpandableViewState() expandableViewState.headsUpIsVisible = true - stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState, - /* isShadeExpanded= */ true, - /* mustStayOnScreen= */ false, - /* isViewEndVisible= */ true, - /* viewEnd= */ 10f, - /* maxHunY= */ 1f) + stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible( + expandableViewState, + /* isShadeExpanded= */ true, + /* mustStayOnScreen= */ false, + /* isViewEndVisible= */ true, + /* viewEnd= */ 10f, + /* maxHunY= */ 1f + ) assertTrue(expandableViewState.headsUpIsVisible) } @@ -529,12 +635,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() { val expandableViewState = ExpandableViewState() expandableViewState.headsUpIsVisible = true - stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState, - /* isShadeExpanded= */ true, - /* mustStayOnScreen= */ true, - /* isViewEndVisible= */ false, - /* viewEnd= */ 10f, - /* maxHunY= */ 1f) + stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible( + expandableViewState, + /* isShadeExpanded= */ true, + /* mustStayOnScreen= */ true, + /* isViewEndVisible= */ false, + /* viewEnd= */ 10f, + /* maxHunY= */ 1f + ) assertTrue(expandableViewState.headsUpIsVisible) } @@ -544,9 +652,12 @@ class StackScrollAlgorithmTest : SysuiTestCase() { val expandableViewState = ExpandableViewState() expandableViewState.yTranslation = 50f - stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f, - /* stackTranslation= */ 0f, - /* collapsedHeight= */ 1f, expandableViewState) + stackScrollAlgorithm.clampHunToTop( + /* quickQsOffsetHeight= */ 10f, + /* stackTranslation= */ 0f, + /* collapsedHeight= */ 1f, + expandableViewState + ) // qqs (10 + 0) < viewY (50) assertEquals(50f, expandableViewState.yTranslation) @@ -557,9 +668,12 @@ class StackScrollAlgorithmTest : SysuiTestCase() { val expandableViewState = ExpandableViewState() expandableViewState.yTranslation = -10f - stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f, - /* stackTranslation= */ 0f, - /* collapsedHeight= */ 1f, expandableViewState) + stackScrollAlgorithm.clampHunToTop( + /* quickQsOffsetHeight= */ 10f, + /* stackTranslation= */ 0f, + /* collapsedHeight= */ 1f, + expandableViewState + ) // qqs (10 + 0) > viewY (-10) assertEquals(10f, expandableViewState.yTranslation) @@ -571,9 +685,12 @@ class StackScrollAlgorithmTest : SysuiTestCase() { expandableViewState.height = 20 expandableViewState.yTranslation = -100f - stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f, - /* stackTranslation= */ 0f, - /* collapsedHeight= */ 10f, expandableViewState) + stackScrollAlgorithm.clampHunToTop( + /* quickQsOffsetHeight= */ 10f, + /* stackTranslation= */ 0f, + /* collapsedHeight= */ 10f, + expandableViewState + ) // newTranslation = max(10, -100) = 10 // distToRealY = 10 - (-100f) = 110 @@ -587,9 +704,12 @@ class StackScrollAlgorithmTest : SysuiTestCase() { expandableViewState.height = 20 expandableViewState.yTranslation = 5f - stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f, - /* stackTranslation= */ 0f, - /* collapsedHeight= */ 10f, expandableViewState) + stackScrollAlgorithm.clampHunToTop( + /* quickQsOffsetHeight= */ 10f, + /* stackTranslation= */ 0f, + /* collapsedHeight= */ 10f, + expandableViewState + ) // newTranslation = max(10, 5) = 10 // distToRealY = 10 - 5 = 5 @@ -599,41 +719,49 @@ class StackScrollAlgorithmTest : SysuiTestCase() { @Test fun computeCornerRoundnessForPinnedHun_stackBelowScreen_round() { - val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun( + val currentRoundness = + stackScrollAlgorithm.computeCornerRoundnessForPinnedHun( /* hostViewHeight= */ 100f, /* stackY= */ 110f, /* viewMaxHeight= */ 20f, - /* originalCornerRoundness= */ 0f) + /* originalCornerRoundness= */ 0f + ) assertEquals(1f, currentRoundness) } @Test fun computeCornerRoundnessForPinnedHun_stackAboveScreenBelowPinPoint_halfRound() { - val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun( + val currentRoundness = + stackScrollAlgorithm.computeCornerRoundnessForPinnedHun( /* hostViewHeight= */ 100f, /* stackY= */ 90f, /* viewMaxHeight= */ 20f, - /* originalCornerRoundness= */ 0f) + /* originalCornerRoundness= */ 0f + ) assertEquals(0.5f, currentRoundness) } @Test fun computeCornerRoundnessForPinnedHun_stackAbovePinPoint_notRound() { - val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun( + val currentRoundness = + stackScrollAlgorithm.computeCornerRoundnessForPinnedHun( /* hostViewHeight= */ 100f, /* stackY= */ 0f, /* viewMaxHeight= */ 20f, - /* originalCornerRoundness= */ 0f) + /* originalCornerRoundness= */ 0f + ) assertEquals(0f, currentRoundness) } @Test fun computeCornerRoundnessForPinnedHun_originallyRoundAndStackAbovePinPoint_round() { - val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun( + val currentRoundness = + stackScrollAlgorithm.computeCornerRoundnessForPinnedHun( /* hostViewHeight= */ 100f, /* stackY= */ 0f, /* viewMaxHeight= */ 20f, - /* originalCornerRoundness= */ 1f) + /* originalCornerRoundness= */ 1f + ) assertEquals(1f, currentRoundness) } @@ -642,23 +770,20 @@ class StackScrollAlgorithmTest : SysuiTestCase() { // Given: shade is opened, yTranslation of HUN is 0, // the height of HUN equals to the height of QQS Panel, // and HUN fully overlaps with QQS Panel - ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) + - px(R.dimen.qqs_layout_padding_bottom) - val childHunView = createHunViewMock( - isShadeOpen = true, - fullyVisible = false, - headerVisibleAmount = 1f - ) + ambientState.stackTranslation = + px(R.dimen.qqs_layout_margin_top) + px(R.dimen.qqs_layout_padding_bottom) + val childHunView = + createHunViewMock(isShadeOpen = true, fullyVisible = false, headerVisibleAmount = 1f) val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() algorithmState.visibleChildren.add(childHunView) // When: updateChildZValue() is called for the top HUN stackScrollAlgorithm.updateChildZValue( - /* i= */ 0, - /* childrenOnTop= */ 0.0f, - /* StackScrollAlgorithmState= */ algorithmState, - /* ambientState= */ ambientState, - /* shouldElevateHun= */ true + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true ) // Then: full shadow would be applied @@ -670,13 +795,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() { // Given: shade is opened, yTranslation of HUN is greater than 0, // the height of HUN is equal to the height of QQS Panel, // and HUN partially overlaps with QQS Panel - ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) + - px(R.dimen.qqs_layout_padding_bottom) - val childHunView = createHunViewMock( - isShadeOpen = true, - fullyVisible = false, - headerVisibleAmount = 1f - ) + ambientState.stackTranslation = + px(R.dimen.qqs_layout_margin_top) + px(R.dimen.qqs_layout_padding_bottom) + val childHunView = + createHunViewMock(isShadeOpen = true, fullyVisible = false, headerVisibleAmount = 1f) // Use half of the HUN's height as overlap childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat() val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() @@ -684,17 +806,17 @@ class StackScrollAlgorithmTest : SysuiTestCase() { // When: updateChildZValue() is called for the top HUN stackScrollAlgorithm.updateChildZValue( - /* i= */ 0, - /* childrenOnTop= */ 0.0f, - /* StackScrollAlgorithmState= */ algorithmState, - /* ambientState= */ ambientState, - /* shouldElevateHun= */ true + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true ) // Then: HUN should have shadow, but not as full size assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f) assertThat(childHunView.viewState.zTranslation) - .isLessThan(px(R.dimen.heads_up_pinned_elevation)) + .isLessThan(px(R.dimen.heads_up_pinned_elevation)) } @Test @@ -702,28 +824,25 @@ class StackScrollAlgorithmTest : SysuiTestCase() { // Given: shade is opened, yTranslation of HUN is equal to QQS Panel's height, // the height of HUN is equal to the height of QQS Panel, // and HUN doesn't overlap with QQS Panel - ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) + - px(R.dimen.qqs_layout_padding_bottom) + ambientState.stackTranslation = + px(R.dimen.qqs_layout_margin_top) + px(R.dimen.qqs_layout_padding_bottom) // Mock the height of shade ambientState.setLayoutMinHeight(1000) - val childHunView = createHunViewMock( - isShadeOpen = true, - fullyVisible = true, - headerVisibleAmount = 1f - ) + val childHunView = + createHunViewMock(isShadeOpen = true, fullyVisible = true, headerVisibleAmount = 1f) // HUN doesn't overlap with QQS Panel - childHunView.viewState.yTranslation = ambientState.topPadding + - ambientState.stackTranslation + childHunView.viewState.yTranslation = + ambientState.topPadding + ambientState.stackTranslation val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() algorithmState.visibleChildren.add(childHunView) // When: updateChildZValue() is called for the top HUN stackScrollAlgorithm.updateChildZValue( - /* i= */ 0, - /* childrenOnTop= */ 0.0f, - /* StackScrollAlgorithmState= */ algorithmState, - /* ambientState= */ ambientState, - /* shouldElevateHun= */ true + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true ) // Then: HUN should not have shadow @@ -737,11 +856,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() { ambientState.stackTranslation = -ambientState.topPadding // Mock the height of shade ambientState.setLayoutMinHeight(1000) - val childHunView = createHunViewMock( - isShadeOpen = false, - fullyVisible = false, - headerVisibleAmount = 0f - ) + val childHunView = + createHunViewMock(isShadeOpen = false, fullyVisible = false, headerVisibleAmount = 0f) childHunView.viewState.yTranslation = 0f // Shade is closed, thus childHunView's headerVisibleAmount is 0 childHunView.headerVisibleAmount = 0f @@ -750,11 +866,11 @@ class StackScrollAlgorithmTest : SysuiTestCase() { // When: updateChildZValue() is called for the top HUN stackScrollAlgorithm.updateChildZValue( - /* i= */ 0, - /* childrenOnTop= */ 0.0f, - /* StackScrollAlgorithmState= */ algorithmState, - /* ambientState= */ ambientState, - /* shouldElevateHun= */ true + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true ) // Then: HUN should have full shadow @@ -768,11 +884,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() { ambientState.stackTranslation = -ambientState.topPadding // Mock the height of shade ambientState.setLayoutMinHeight(1000) - val childHunView = createHunViewMock( - isShadeOpen = false, - fullyVisible = false, - headerVisibleAmount = 0.5f - ) + val childHunView = + createHunViewMock(isShadeOpen = false, fullyVisible = false, headerVisibleAmount = 0.5f) childHunView.viewState.yTranslation = 0f // Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1 // use 0.5 as headerVisibleAmount here @@ -782,17 +895,17 @@ class StackScrollAlgorithmTest : SysuiTestCase() { // When: updateChildZValue() is called for the top HUN stackScrollAlgorithm.updateChildZValue( - /* i= */ 0, - /* childrenOnTop= */ 0.0f, - /* StackScrollAlgorithmState= */ algorithmState, - /* ambientState= */ ambientState, - /* shouldElevateHun= */ true + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true ) // Then: HUN should have shadow, but not as full size assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f) assertThat(childHunView.viewState.zTranslation) - .isLessThan(px(R.dimen.heads_up_pinned_elevation)) + .isLessThan(px(R.dimen.heads_up_pinned_elevation)) } @Test @@ -862,134 +975,174 @@ class StackScrollAlgorithmTest : SysuiTestCase() { // stackScrollAlgorithm.resetViewStates is called. ambientState.dozeAmount = 0.5f setExpansionFractionWithoutShelfDuringAodToLockScreen( - ambientState, - algorithmState, - fraction = 0.5f + ambientState, + algorithmState, + fraction = 0.5f ) stackScrollAlgorithm.resetViewStates(ambientState, 0) // Then: pulsingNotificationView should show at full height assertEquals( - stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView), - pulsingNotificationView.viewState.height + stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView), + pulsingNotificationView.viewState.height ) // After: reset dozeAmount and expansionFraction ambientState.dozeAmount = 0f setExpansionFractionWithoutShelfDuringAodToLockScreen( - ambientState, - algorithmState, - fraction = 1f + ambientState, + algorithmState, + fraction = 1f ) } // region shouldPinHunToBottomOfExpandedQs @Test fun shouldHunBeVisibleWhenScrolled_mustStayOnScreenFalse_false() { - assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled( - /* mustStayOnScreen= */false, - /* headsUpIsVisible= */false, - /* showingPulsing= */false, - /* isOnKeyguard=*/false, - /*headsUpOnKeyguard=*/false - )).isFalse() + assertThat( + stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled( + /* mustStayOnScreen= */ false, + /* headsUpIsVisible= */ false, + /* showingPulsing= */ false, + /* isOnKeyguard=*/ false, + /*headsUpOnKeyguard=*/ false + ) + ) + .isFalse() } @Test fun shouldPinHunToBottomOfExpandedQs_headsUpIsVisible_false() { - assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled( - /* mustStayOnScreen= */true, - /* headsUpIsVisible= */true, - /* showingPulsing= */false, - /* isOnKeyguard=*/false, - /*headsUpOnKeyguard=*/false - )).isFalse() + assertThat( + stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled( + /* mustStayOnScreen= */ true, + /* headsUpIsVisible= */ true, + /* showingPulsing= */ false, + /* isOnKeyguard=*/ false, + /*headsUpOnKeyguard=*/ false + ) + ) + .isFalse() } @Test fun shouldHunBeVisibleWhenScrolled_showingPulsing_false() { - assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled( - /* mustStayOnScreen= */true, - /* headsUpIsVisible= */false, - /* showingPulsing= */true, - /* isOnKeyguard=*/false, - /* headsUpOnKeyguard= */false - )).isFalse() + assertThat( + stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled( + /* mustStayOnScreen= */ true, + /* headsUpIsVisible= */ false, + /* showingPulsing= */ true, + /* isOnKeyguard=*/ false, + /* headsUpOnKeyguard= */ false + ) + ) + .isFalse() } @Test fun shouldHunBeVisibleWhenScrolled_isOnKeyguard_false() { - assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled( - /* mustStayOnScreen= */true, - /* headsUpIsVisible= */false, - /* showingPulsing= */false, - /* isOnKeyguard=*/true, - /* headsUpOnKeyguard= */false - )).isFalse() + assertThat( + stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled( + /* mustStayOnScreen= */ true, + /* headsUpIsVisible= */ false, + /* showingPulsing= */ false, + /* isOnKeyguard=*/ true, + /* headsUpOnKeyguard= */ false + ) + ) + .isFalse() } @Test fun shouldHunBeVisibleWhenScrolled_isNotOnKeyguard_true() { - assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled( - /* mustStayOnScreen= */true, - /* headsUpIsVisible= */false, - /* showingPulsing= */false, - /* isOnKeyguard=*/false, - /* headsUpOnKeyguard= */false - )).isTrue() + assertThat( + stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled( + /* mustStayOnScreen= */ true, + /* headsUpIsVisible= */ false, + /* showingPulsing= */ false, + /* isOnKeyguard=*/ false, + /* headsUpOnKeyguard= */ false + ) + ) + .isTrue() } @Test fun shouldHunBeVisibleWhenScrolled_headsUpOnKeyguard_true() { - assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled( - /* mustStayOnScreen= */true, - /* headsUpIsVisible= */false, - /* showingPulsing= */false, - /* isOnKeyguard=*/true, - /* headsUpOnKeyguard= */true - )).isTrue() + assertThat( + stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled( + /* mustStayOnScreen= */ true, + /* headsUpIsVisible= */ false, + /* showingPulsing= */ false, + /* isOnKeyguard=*/ true, + /* headsUpOnKeyguard= */ true + ) + ) + .isTrue() } - // endregion - private fun createHunViewMock( - isShadeOpen: Boolean, - fullyVisible: Boolean, - headerVisibleAmount: Float - ) = - mock<ExpandableNotificationRow>().apply { - val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible) - whenever(this.viewState).thenReturn(childViewStateMock) - - whenever(this.mustStayOnScreen()).thenReturn(true) - whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount) + @Test + fun shouldHunAppearFromBottom_hunAtMaxHunTranslation() { + ambientState.maxHeadsUpTranslation = 400f + val viewState = + ExpandableViewState().apply { + height = 100 + yTranslation = ambientState.maxHeadsUpTranslation - height // move it to the max } + assertTrue(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState)) + } - private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) = + @Test + fun shouldHunAppearFromBottom_hunBelowMaxHunTranslation() { + ambientState.maxHeadsUpTranslation = 400f + val viewState = ExpandableViewState().apply { - // Mock the HUN's height with ambientState.topPadding + - // ambientState.stackTranslation - height = (ambientState.topPadding + ambientState.stackTranslation).toInt() - if (isShadeOpen && fullyVisible) { - yTranslation = - ambientState.topPadding + ambientState.stackTranslation - } else { - yTranslation = 0f - } - headsUpIsVisible = fullyVisible + height = 100 + yTranslation = + ambientState.maxHeadsUpTranslation - height - 1 // move it below the max } - private fun createPulsingViewMock( + assertFalse(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState)) + } + // endregion + + private fun createHunViewMock( + isShadeOpen: Boolean, + fullyVisible: Boolean, + headerVisibleAmount: Float ) = - mock<ExpandableNotificationRow>().apply { - whenever(this.viewState).thenReturn(ExpandableViewState()) - whenever(this.showingPulsing()).thenReturn(true) + mock<ExpandableNotificationRow>().apply { + val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible) + whenever(this.viewState).thenReturn(childViewStateMock) + + whenever(this.mustStayOnScreen()).thenReturn(true) + whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount) + } + + private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) = + ExpandableViewState().apply { + // Mock the HUN's height with ambientState.topPadding + + // ambientState.stackTranslation + height = (ambientState.topPadding + ambientState.stackTranslation).toInt() + if (isShadeOpen && fullyVisible) { + yTranslation = ambientState.topPadding + ambientState.stackTranslation + } else { + yTranslation = 0f } + headsUpIsVisible = fullyVisible + } + + private fun createPulsingViewMock() = + mock<ExpandableNotificationRow>().apply { + whenever(this.viewState).thenReturn(ExpandableViewState()) + whenever(this.showingPulsing()).thenReturn(true) + } private fun setExpansionFractionWithoutShelfDuringAodToLockScreen( - ambientState: AmbientState, - algorithmState: StackScrollAlgorithm.StackScrollAlgorithmState, - fraction: Float + ambientState: AmbientState, + algorithmState: StackScrollAlgorithm.StackScrollAlgorithmState, + fraction: Float ) { // showingShelf: false algorithmState.firstViewInShelf = null @@ -1002,11 +1155,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() { ambientState.stackHeight = ambientState.stackEndHeight * fraction } - private fun resetViewStates_hunYTranslationIsInset() { + private fun resetViewStates_hunYTranslationIs(expected: Float) { stackScrollAlgorithm.resetViewStates(ambientState, 0) - assertThat(notificationRow.viewState.yTranslation) - .isEqualTo(stackScrollAlgorithm.mHeadsUpInset) + assertThat(notificationRow.viewState.yTranslation).isEqualTo(expected) } private fun resetViewStates_stackMargin_changesHunYTranslation() { @@ -1025,13 +1177,13 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } private fun resetViewStates_hunsOverlapping_bottomHunClipped( - topHun: ExpandableNotificationRow, - bottomHun: ExpandableNotificationRow + topHun: ExpandableNotificationRow, + bottomHun: ExpandableNotificationRow ) { - val topHunHeight = mContext.resources.getDimensionPixelSize( - R.dimen.notification_content_min_height) - val bottomHunHeight = mContext.resources.getDimensionPixelSize( - R.dimen.notification_max_heads_up_height) + val topHunHeight = + mContext.resources.getDimensionPixelSize(R.dimen.notification_content_min_height) + val bottomHunHeight = + mContext.resources.getDimensionPixelSize(R.dimen.notification_max_heads_up_height) whenever(topHun.intrinsicHeight).thenReturn(topHunHeight) whenever(bottomHun.intrinsicHeight).thenReturn(bottomHunHeight) @@ -1054,8 +1206,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } private fun resetViewStates_expansionChanging_notificationAlphaUpdated( - expansionFraction: Float, - expectedAlpha: Float, + expansionFraction: Float, + expectedAlpha: Float, ) { ambientState.isExpansionChanging = true ambientState.expansionFraction = expansionFraction diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt new file mode 100644 index 000000000000..5a5703512a39 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt @@ -0,0 +1,125 @@ +/* + * 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.stack + +import android.platform.test.annotations.EnableFlags +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent +import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR +import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mockito.any +import org.mockito.Mockito.description +import org.mockito.Mockito.eq +import org.mockito.Mockito.verify + +private const val VIEW_HEIGHT = 100 + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class StackStateAnimatorTest : SysuiTestCase() { + + private lateinit var stackStateAnimator: StackStateAnimator + private val stackScroller: NotificationStackScrollLayout = mock() + private val view: ExpandableView = mock() + private val viewState: ExpandableViewState = + ExpandableViewState().apply { height = VIEW_HEIGHT } + private val runnableCaptor: ArgumentCaptor<Runnable> = argumentCaptor() + @Before + fun setUp() { + whenever(stackScroller.context).thenReturn(context) + whenever(view.viewState).thenReturn(viewState) + stackStateAnimator = StackStateAnimator(stackScroller) + } + + @Test + @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) + fun startAnimationForEvents_headsUpFromTop_startsHeadsUpAppearAnim() { + val topMargin = 50f + val expectedStartY = -topMargin - stackStateAnimator.mHeadsUpAppearStartAboveScreen + val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) + stackStateAnimator.setStackTopMargin(topMargin.toInt()) + + stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0) + + verify(view).setActualHeight(VIEW_HEIGHT, false) + verify(view, description("should animate from the top")).translationY = expectedStartY + verify(view) + .performAddAnimation( + /* delay= */ 0L, + /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(), + /* isHeadsUpAppear= */ true, + /* onEndRunnable= */ null + ) + } + + @Test + @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) + fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim() { + val screenHeight = 2000f + val expectedStartY = screenHeight + stackStateAnimator.mHeadsUpAppearStartAboveScreen + val event = + AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR).apply { + headsUpFromBottom = true + } + stackStateAnimator.setHeadsUpAppearHeightBottom(screenHeight.toInt()) + + stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0) + + verify(view).setActualHeight(VIEW_HEIGHT, false) + verify(view, description("should animate from the bottom")).translationY = expectedStartY + verify(view) + .performAddAnimation( + /* delay= */ 0L, + /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(), + /* isHeadsUpAppear= */ true, + /* onEndRunnable= */ null + ) + } + + @Test + fun startAnimationForEvents_startsHeadsUpDisappearAnim() { + val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) + stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0) + + verify(view) + .performRemoveAnimation( + /* duration= */ eq(ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong()), + /* delay= */ eq(0L), + /* translationDirection= */ eq(0f), + /* isHeadsUpAnimation= */ eq(true), + /* onStartedRunnable= */ any(), + /* onFinishedRunnable= */ runnableCaptor.capture(), + /* animationListener= */ any() + ) + + runnableCaptor.value.run() // execute the end runnable + + verify(view, description("should be called at the end of the animation")) + .removeFromTransientContainer() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt index da543d4454b8..cd6bb5f4966a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt @@ -17,10 +17,10 @@ package com.android.systemui.statusbar.notification.stack import android.testing.AndroidTestingRunner -import android.util.Log -import android.util.Log.TerribleFailureHandler import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.log.assertDoesNotLogWtf +import com.android.systemui.log.assertLogsWtf import kotlin.math.log2 import kotlin.math.sqrt import org.junit.Assert @@ -32,61 +32,36 @@ import org.junit.runner.RunWith class ViewStateTest : SysuiTestCase() { private val viewState = ViewState() - private var wtfHandler: TerribleFailureHandler? = null - private var wtfCount = 0 - @Suppress("DIVISION_BY_ZERO") @Test fun testWtfs() { - interceptWtfs() - // Setting valid values doesn't cause any wtfs. - viewState.alpha = 0.1f - viewState.xTranslation = 0f - viewState.yTranslation = 10f - viewState.zTranslation = 20f - viewState.scaleX = 0.5f - viewState.scaleY = 0.25f - - expectWtfs(0) + assertDoesNotLogWtf { + viewState.alpha = 0.1f + viewState.xTranslation = 0f + viewState.yTranslation = 10f + viewState.zTranslation = 20f + viewState.scaleX = 0.5f + viewState.scaleY = 0.25f + } // Setting NaN values leads to wtfs being logged, and the value not being changed. - viewState.alpha = 0.0f / 0.0f - expectWtfs(1) + assertLogsWtf { viewState.alpha = 0.0f / 0.0f } Assert.assertEquals(viewState.alpha, 0.1f) - viewState.xTranslation = Float.NaN - expectWtfs(2) + assertLogsWtf { viewState.xTranslation = Float.NaN } Assert.assertEquals(viewState.xTranslation, 0f) - viewState.yTranslation = log2(-10.0).toFloat() - expectWtfs(3) + assertLogsWtf { viewState.yTranslation = log2(-10.0).toFloat() } Assert.assertEquals(viewState.yTranslation, 10f) - viewState.zTranslation = sqrt(-1.0).toFloat() - expectWtfs(4) + assertLogsWtf { viewState.zTranslation = sqrt(-1.0).toFloat() } Assert.assertEquals(viewState.zTranslation, 20f) - viewState.scaleX = Float.POSITIVE_INFINITY + Float.NEGATIVE_INFINITY - expectWtfs(5) + assertLogsWtf { viewState.scaleX = Float.POSITIVE_INFINITY + Float.NEGATIVE_INFINITY } Assert.assertEquals(viewState.scaleX, 0.5f) - viewState.scaleY = Float.POSITIVE_INFINITY * 0 - expectWtfs(6) + assertLogsWtf { viewState.scaleY = Float.POSITIVE_INFINITY * 0 } Assert.assertEquals(viewState.scaleY, 0.25f) } - - private fun interceptWtfs() { - wtfCount = 0 - wtfHandler = - Log.setWtfHandler { _: String?, e: Log.TerribleFailure, _: Boolean -> - Log.e("ViewStateTest", "Observed WTF: $e") - wtfCount++ - } - } - - private fun expectWtfs(expectedWtfCount: Int) { - Assert.assertNotNull(wtfHandler) - Assert.assertEquals(expectedWtfCount, wtfCount) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 1cc611c2df87..14751c2dc29e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -43,7 +43,6 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.animation.AnimatorTestRule; -import com.android.systemui.common.ui.ConfigurationState; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; import com.android.systemui.log.LogBuffer; @@ -57,9 +56,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore; -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; @@ -70,7 +67,6 @@ import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder; import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.ui.SystemBarUtilsState; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateListener; import com.android.systemui.util.CarrierConfigTracker; @@ -702,7 +698,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mKeyguardStateController, mShadeViewController, mStatusBarStateController, - mock(StatusBarIconViewBindingFailureTracker.class), + mock(NotificationIconContainerStatusBarViewBinder.class), mCommandQueue, mCarrierConfigTracker, new CollapsedStatusBarFragmentLogger( @@ -715,10 +711,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mDumpManager, mStatusBarWindowStateController, mKeyguardUpdateMonitor, - mock(NotificationIconContainerStatusBarViewModel.class), - mock(ConfigurationState.class), - mock(SystemBarUtilsState.class), - mock(StatusBarNotificationIconViewStore.class), mock(DemoModeController.class)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt index bf851eb69a0d..6714c94b017c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt @@ -1115,6 +1115,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { broadcastDispatcher = fakeBroadcastDispatcher, keyguardUpdateMonitor = keyguardUpdateMonitor, backgroundDispatcher = utils.testDispatcher, + mainDispatcher = utils.testDispatcher, activityManager = activityManager, refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt index d1870b1d8fcd..21d4549c904d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt @@ -258,6 +258,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { broadcastDispatcher = fakeBroadcastDispatcher, keyguardUpdateMonitor = keyguardUpdateMonitor, backgroundDispatcher = testDispatcher, + mainDispatcher = testDispatcher, activityManager = activityManager, refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = guestUserInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index b7b24f6dc7dd..d0804be81072 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -170,6 +170,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { broadcastDispatcher = fakeBroadcastDispatcher, keyguardUpdateMonitor = keyguardUpdateMonitor, backgroundDispatcher = testDispatcher, + mainDispatcher = testDispatcher, activityManager = activityManager, refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = guestUserInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java deleted file mode 100644 index a2b016f22473..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2017 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.leak; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.concurrency.MessageRouterImpl; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class GarbageMonitorTest extends SysuiTestCase { - - @Mock private LeakReporter mLeakReporter; - @Mock private TrackedGarbage mTrackedGarbage; - @Mock private DumpManager mDumpManager; - private GarbageMonitor mGarbageMonitor; - private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mGarbageMonitor = - new GarbageMonitor( - mContext, - mFakeExecutor, - new MessageRouterImpl(mFakeExecutor), - new LeakDetector(null, mTrackedGarbage, null, mDumpManager), - mLeakReporter, - mDumpManager); - } - - @Test - public void testALittleGarbage_doesntDump() { - when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE); - - mGarbageMonitor.reinspectGarbageAfterGc(); - - verify(mLeakReporter, never()).dumpLeak(anyInt()); - } - - @Test - public void testTransientGarbage_doesntDump() { - when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1); - - // Start the leak monitor. Nothing gets reported immediately. - mGarbageMonitor.startLeakMonitor(); - mFakeExecutor.runAllReady(); - verify(mLeakReporter, never()).dumpLeak(anyInt()); - - // Garbage gets reset to 0 before the leak reporte actually gets called. - when(mTrackedGarbage.countOldGarbage()).thenReturn(0); - mFakeExecutor.advanceClockToLast(); - mFakeExecutor.runAllReady(); - - // Therefore nothing gets dumped. - verify(mLeakReporter, never()).dumpLeak(anyInt()); - } - - @Test - public void testLotsOfPersistentGarbage_dumps() { - when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1); - - mGarbageMonitor.reinspectGarbageAfterGc(); - - verify(mLeakReporter).dumpLeak(GarbageMonitor.GARBAGE_ALLOWANCE + 1); - } - - @Test - public void testLotsOfPersistentGarbage_dumpsAfterAtime() { - when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1); - - // Start the leak monitor. Nothing gets reported immediately. - mGarbageMonitor.startLeakMonitor(); - mFakeExecutor.runAllReady(); - verify(mLeakReporter, never()).dumpLeak(anyInt()); - - mFakeExecutor.advanceClockToLast(); - mFakeExecutor.runAllReady(); - - verify(mLeakReporter).dumpLeak(GarbageMonitor.GARBAGE_ALLOWANCE + 1); - } -}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt index e59e4759fc7b..78016840c3fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt @@ -28,8 +28,8 @@ import androidx.test.filters.SmallTest import com.android.systemui.model.SysUiStateTest import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.BubbleEducationController -import com.android.wm.shell.bubbles.PREF_MANAGED_EDUCATION -import com.android.wm.shell.bubbles.PREF_STACK_EDUCATION +import com.android.wm.shell.bubbles.ManageEducationView.Companion.PREF_MANAGED_EDUCATION +import com.android.wm.shell.bubbles.StackEducationView.Companion.PREF_STACK_EDUCATION import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor import org.junit.Assert.assertEquals diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index b217195000b4..814ea19a7dcb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -29,8 +29,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.google.common.truth.Truth.assertThat; -import static kotlinx.coroutines.flow.FlowKt.emptyFlow; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -50,6 +48,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import android.app.ActivityManager; import android.app.IActivityManager; import android.app.INotificationManager; @@ -186,7 +186,7 @@ import com.android.wm.shell.bubbles.BubbleStackView; import com.android.wm.shell.bubbles.BubbleViewInfoTask; import com.android.wm.shell.bubbles.BubbleViewProvider; import com.android.wm.shell.bubbles.Bubbles; -import com.android.wm.shell.bubbles.StackEducationViewKt; +import com.android.wm.shell.bubbles.StackEducationView; import com.android.wm.shell.bubbles.properties.BubbleProperties; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.FloatingContentCoordinator; @@ -1930,7 +1930,7 @@ public class BubblesTest extends SysuiTestCase { @Test public void testShowStackEdu_isNotConversationBubble() { // Setup - setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false); + setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, false); BubbleEntry bubbleEntry = createBubbleEntry(false /* isConversation */); mBubbleController.updateBubble(bubbleEntry); assertTrue(mBubbleController.hasBubbles()); @@ -1948,7 +1948,7 @@ public class BubblesTest extends SysuiTestCase { @Test public void testShowStackEdu_isConversationBubble() { // Setup - setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false); + setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, false); BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */); mBubbleController.updateBubble(bubbleEntry); assertTrue(mBubbleController.hasBubbles()); @@ -1966,7 +1966,7 @@ public class BubblesTest extends SysuiTestCase { @Test public void testShowStackEdu_isSeenConversationBubble() { // Setup - setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, true); + setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, true); BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */); mBubbleController.updateBubble(bubbleEntry); assertTrue(mBubbleController.hasBubbles()); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt index a464fa80d629..fa7958041641 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt @@ -16,10 +16,8 @@ package com.android.systemui.accessibility.data.repository -import android.view.accessibility.accessibilityManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -val Kosmos.accessibilityRepository by Fixture { - AccessibilityRepository.invoke(a11yManager = accessibilityManager) -} +val Kosmos.fakeAccessibilityRepository by Fixture { FakeAccessibilityRepository() } +val Kosmos.accessibilityRepository by Fixture { fakeAccessibilityRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt index 7c5696c716d8..848650810582 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt @@ -20,7 +20,6 @@ import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel.SecurityMode -import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationResultModel @@ -29,7 +28,6 @@ import dagger.Binds import dagger.Module import dagger.Provides import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -40,15 +38,11 @@ class FakeAuthenticationRepository( private val currentTime: () -> Long, ) : AuthenticationRepository { - override val authenticationChallengeResult = MutableSharedFlow<Boolean>() - override val hintedPinLength: Int = HINTING_PIN_LENGTH private val _isPatternVisible = MutableStateFlow(true) override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow() - override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null) - override val hasLockoutOccurred = MutableStateFlow(false) private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false) @@ -68,8 +62,6 @@ class FakeAuthenticationRepository( override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = _isPinEnhancedPrivacyEnabled.asStateFlow() - private var failedAttemptCount = 0 - private var lockoutEndTimestamp = 0L private var credentialOverride: List<Any>? = null private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode() @@ -89,11 +81,27 @@ class FakeAuthenticationRepository( } override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) { - failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1 - authenticationChallengeResult.emit(isSuccessful) + if (isSuccessful) { + _failedAuthenticationAttempts.value = 0 + _lockoutEndTimestamp = null + hasLockoutOccurred.value = false + lockoutStartedReportCount = 0 + } else { + _failedAuthenticationAttempts.value++ + } } + private var _failedAuthenticationAttempts = MutableStateFlow(0) + override val failedAuthenticationAttempts: StateFlow<Int> = + _failedAuthenticationAttempts.asStateFlow() + + private var _lockoutEndTimestamp: Long? = null + override val lockoutEndTimestamp: Long? + get() = if (currentTime() < (_lockoutEndTimestamp ?: 0)) _lockoutEndTimestamp else null + override suspend fun reportLockoutStarted(durationMs: Int) { + _lockoutEndTimestamp = (currentTime() + durationMs).takeIf { durationMs > 0 } + hasLockoutOccurred.value = true lockoutStartedReportCount++ } @@ -101,25 +109,10 @@ class FakeAuthenticationRepository( return (credentialOverride ?: DEFAULT_PIN).size } - override suspend fun getFailedAuthenticationAttemptCount(): Int { - return failedAttemptCount - } - - override suspend fun getLockoutEndTimestamp(): Long { - return lockoutEndTimestamp - } - fun setAutoConfirmFeatureEnabled(isEnabled: Boolean) { _isAutoConfirmFeatureEnabled.value = isEnabled } - override suspend fun setLockoutDuration(durationMs: Int) { - lockoutEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0 - if (durationMs > 0) { - hasLockoutOccurred.value = true - } - } - override suspend fun checkCredential( credential: LockscreenCredential ): AuthenticationResultModel { @@ -136,8 +129,8 @@ class FakeAuthenticationRepository( else -> error("Unexpected credential type ${credential.type}!") } - return if (isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) { - hasLockoutOccurred.value = false + val failedAttempts = _failedAuthenticationAttempts.value + return if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) { AuthenticationResultModel( isSuccessful = isSuccessful, lockoutDurationMs = 0, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt index 060ca4c7e912..05cb059a00cd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt @@ -19,17 +19,11 @@ package com.android.systemui.authentication.domain.interactor import com.android.systemui.authentication.data.repository.authenticationRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope -import com.android.systemui.kosmos.testDispatcher -import com.android.systemui.user.data.repository.userRepository -import com.android.systemui.util.time.fakeSystemClock val Kosmos.authenticationInteractor by Kosmos.Fixture { AuthenticationInteractor( applicationScope = applicationCoroutineScope, repository = authenticationRepository, - backgroundDispatcher = testDispatcher, - userRepository = userRepository, - clock = fakeSystemClock, ) } diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt index 6c1f0fcd622a..7f707850e3dd 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt @@ -14,11 +14,10 @@ * limitations under the License. */ -package android.companion.virtual.camera; +package com.android.systemui.biometrics -/** - * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with - * VirtualCamera. - * @hide - */ -parcelable VirtualCameraMetadata; +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.util.mockito.mock + +var Kosmos.authController by Fixture { mock<AuthController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt new file mode 100644 index 000000000000..cbfc7686a896 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt @@ -0,0 +1,33 @@ +/* + * 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.biometrics.domain.interactor + +import android.content.applicationContext +import com.android.systemui.biometrics.authController +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.user.domain.interactor.selectedUserInteractor + +val Kosmos.udfpsOverlayInteractor by Fixture { + UdfpsOverlayInteractor( + context = applicationContext, + authController = authController, + selectedUserInteractor = selectedUserInteractor, + scope = applicationCoroutineScope, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt new file mode 100644 index 000000000000..9175f093c171 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt @@ -0,0 +1,34 @@ +/* + * 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.deviceentry.data.ui.viewmodel + +import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor +import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor +import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel +import com.android.systemui.keyguard.ui.viewmodel.deviceEntryForegroundIconViewModel +import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel +import com.android.systemui.kosmos.Kosmos + +val Kosmos.udfpsAccessibilityOverlayViewModel by + Kosmos.Fixture { + UdfpsAccessibilityOverlayViewModel( + udfpsOverlayInteractor = udfpsOverlayInteractor, + accessibilityInteractor = accessibilityInteractor, + deviceEntryIconViewModel = deviceEntryIconViewModel, + deviceEntryFgIconViewModel = deviceEntryForegroundIconViewModel, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt new file mode 100644 index 000000000000..4bfe4f571b05 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt @@ -0,0 +1,38 @@ +/* + * 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.keyguard.ui.viewmodel + +import android.content.applicationContext +import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +val Kosmos.deviceEntryForegroundIconViewModel by Fixture { + DeviceEntryForegroundViewModel( + context = applicationContext, + configurationRepository = configurationRepository, + deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, + transitionInteractor = keyguardTransitionInteractor, + deviceEntryIconViewModel = deviceEntryIconViewModel, + udfpsOverlayInteractor = udfpsOverlayInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt index 67e9289f5d92..5ceefde32d2a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt @@ -28,8 +28,10 @@ import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager +val Kosmos.fakeDeviceEntryIconViewModelTransition by Fixture { FakeDeviceEntryIconTransition() } + val Kosmos.deviceEntryIconViewModelTransitionsMock by Fixture { - mutableSetOf<DeviceEntryIconTransition>() + setOf<DeviceEntryIconTransition>(fakeDeviceEntryIconViewModelTransition) } val Kosmos.deviceEntryIconViewModel by Fixture { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt index dca151db8b58..6d872a3d8028 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt @@ -16,11 +16,16 @@ package com.android.systemui.keyguard.ui.viewmodel -import android.graphics.Rect -import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow -@ExperimentalCoroutinesApi -class UdfpsKeyguardViewModel @Inject constructor() { - var sensorBounds: Rect = Rect() +class FakeDeviceEntryIconTransition : DeviceEntryIconTransition { + private val _deviceEntryParentViewAlpha: MutableStateFlow<Float> = MutableStateFlow(0f) + override val deviceEntryParentViewAlpha: Flow<Float> = _deviceEntryParentViewAlpha.asStateFlow() + + fun setDeviceEntryParentViewAlpha(alpha: Float) { + _deviceEntryParentViewAlpha.value = alpha + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt index 6ccb3bc2812e..5e67182d7353 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt @@ -20,6 +20,29 @@ import android.util.Log import android.util.Log.TerribleFailureHandler import junit.framework.Assert +/** Asserts that the given block does not make a call to Log.wtf */ +fun assertDoesNotLogWtf( + message: String = "Expected Log.wtf not to be called", + notLoggingBlock: () -> Unit, +) { + var caught: TerribleFailureLog? = null + val newHandler = TerribleFailureHandler { tag, failure, system -> + caught = TerribleFailureLog(tag, failure, system) + } + val oldHandler = Log.setWtfHandler(newHandler) + try { + notLoggingBlock() + } finally { + Log.setWtfHandler(oldHandler) + } + caught?.let { throw AssertionError("$message: $it", it.failure) } +} + +fun assertDoesNotLogWtf( + message: String = "Expected Log.wtf not to be called", + notLoggingRunnable: Runnable, +) = assertDoesNotLogWtf(message = message) { notLoggingRunnable.run() } + /** * Assert that the given block makes a call to Log.wtf * diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 0b41926ed13e..25b97b3dab5d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -91,7 +91,6 @@ import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.data.repository.TelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor import com.android.systemui.user.data.repository.FakeUserRepository -import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.user.ui.viewmodel.UserActionViewModel import com.android.systemui.user.ui.viewmodel.UserViewModel @@ -174,7 +173,7 @@ class SceneTestUtils( mobileConnectionsRepository = mobileConnectionsRepository, ) - val userRepository: UserRepository by lazy { + val userRepository: FakeUserRepository by lazy { FakeUserRepository().apply { val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0)) setUserInfos(users) @@ -236,9 +235,6 @@ class SceneTestUtils( return AuthenticationInteractor( applicationScope = applicationScope(), repository = repository, - backgroundDispatcher = testDispatcher, - userRepository = userRepository, - clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } } ) } @@ -274,7 +270,6 @@ class SceneTestUtils( repository = bouncerRepository, authenticationInteractor = authenticationInteractor, keyguardFaceAuthInteractor = keyguardFaceAuthInteractor, - flags = sceneContainerFlags, falsingInteractor = falsingInteractor(), powerInteractor = powerInteractor(), simBouncerInteractor = simBouncerInteractor, @@ -312,6 +307,7 @@ class SceneTestUtils( userSwitcherMenu = flowOf(createMenuActions()), actionButtonInteractor = actionButtonInteractor, simBouncerInteractor = simBouncerInteractor, + clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt index 5c8fe0d5a81e..2d2f546a05b4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt @@ -24,6 +24,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.statusbar.data.repository.notificationListenerSettingsRepository import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor import com.android.wm.shell.bubbles.bubblesOptional import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -43,6 +44,7 @@ val Kosmos.notificationIconsInteractor by Fixture { NotificationIconsInteractor( activeNotificationsInteractor = activeNotificationsInteractor, bubbles = bubblesOptional, + headsUpNotificationIconInteractor = headsUpNotificationIconInteractor, keyguardViewStateRepository = notificationsKeyguardViewStateRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ShelfNotificationIconViewStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt index f7f16a4671f9..67fecb4d8bcd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ShelfNotificationIconViewStoreKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt @@ -16,9 +16,22 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder +import com.android.systemui.common.ui.configurationState import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerShelfViewModel import com.android.systemui.statusbar.notification.stack.ui.viewbinder.notifCollection +import com.android.systemui.statusbar.ui.systemBarUtilsState + +val Kosmos.notificationIconContainerShelfViewBinder by Fixture { + NotificationIconContainerShelfViewBinder( + notificationIconContainerShelfViewModel, + configurationState, + systemBarUtilsState, + statusBarIconViewBindingFailureTracker, + shelfNotificationIconViewStore, + ) +} val Kosmos.shelfNotificationIconViewStore by Fixture { ShelfNotificationIconViewStore(notifCollection = notifCollection) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt index 988172c7bb13..b906b6060245 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.shelf.ui.viewmodel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerShelfViewModel import com.android.systemui.statusbar.notification.row.ui.viewmodel.activatableNotificationViewModel import com.android.systemui.statusbar.notification.shelf.domain.interactor.notificationShelfInteractor @@ -26,6 +25,5 @@ val Kosmos.notificationShelfViewModel by Fixture { NotificationShelfViewModel( interactor = notificationShelfInteractor, activatableViewModel = activatableNotificationViewModel, - icons = notificationIconContainerShelfViewModel, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt index ca5b4010fd7d..04716b9c48a3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt @@ -22,11 +22,9 @@ import com.android.systemui.common.ui.configurationState import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.shelfNotificationIconViewStore -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.statusBarIconViewBindingFailureTracker +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel import com.android.systemui.statusbar.phone.notificationIconAreaController -import com.android.systemui.statusbar.ui.systemBarUtilsState val Kosmos.notificationListViewBinder by Fixture { NotificationListViewBinder( @@ -35,9 +33,7 @@ val Kosmos.notificationListViewBinder by Fixture { configuration = configurationState, falsingManager = falsingManager, iconAreaController = notificationIconAreaController, - iconViewBindingFailureTracker = statusBarIconViewBindingFailureTracker, metricsLogger = metricsLogger, - shelfIconViewStore = shelfNotificationIconViewStore, - systemBarUtilsState = systemBarUtilsState, + nicBinder = notificationIconContainerShelfViewBinder, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt index 42c77aaac53f..4e2dc7af8cb4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt @@ -47,6 +47,7 @@ val Kosmos.userSwitcherInteractor by broadcastDispatcher = broadcastDispatcher, keyguardUpdateMonitor = keyguardUpdateMonitor, backgroundDispatcher = testDispatcher, + mainDispatcher = testDispatcher, activityManager = activityManager, refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = guestUserInteractor, diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java index df74770fdc0f..62c670317f5f 100644 --- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java @@ -130,7 +130,7 @@ public class CompanionTransportManager { synchronized (mTransports) { for (int i = 0; i < associationIds.length; i++) { if (mTransports.contains(associationIds[i])) { - mTransports.get(associationIds[i]).requestForResponse(message, data); + mTransports.get(associationIds[i]).sendMessage(message, data); } } } @@ -220,7 +220,7 @@ public class CompanionTransportManager { if (transport == null) { return CompletableFuture.failedFuture(new IOException("Missing transport")); } - return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data); + return transport.sendMessage(MESSAGE_REQUEST_PERMISSION_RESTORE, data); } } diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java index 32d4061db1f7..22b18ac9653b 100644 --- a/services/companion/java/com/android/server/companion/transport/Transport.java +++ b/services/companion/java/com/android/server/companion/transport/Transport.java @@ -16,6 +16,9 @@ package com.android.server.companion.transport; +import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE; +import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_PING; +import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_TO_WEARABLE; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING; @@ -80,6 +83,10 @@ public abstract class Transport { return (message & 0xFF000000) == 0x33000000; } + private static boolean isOneway(int message) { + return (message & 0xFF000000) == 0x43000000; + } + @GuardedBy("mPendingRequests") protected final SparseArray<CompletableFuture<byte[]>> mPendingRequests = new SparseArray<>(); @@ -134,6 +141,42 @@ public abstract class Transport { protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data) throws IOException; + /** + * Send a message using this transport. If the message was a request, then the returned Future + * object will complete successfully only if the remote device both received and processed it + * as expected. If the message was a send-and-forget type message, then the Future object will + * resolve successfully immediately (with null) upon sending the message. + * + * @param message the message type + * @param data the message payload + * @return Future object containing the result of the sent message. + */ + public Future<byte[]> sendMessage(int message, byte[] data) { + final CompletableFuture<byte[]> pending = new CompletableFuture<>(); + if (isOneway(message)) { + return sendAndForget(message, data); + } else if (isRequest(message)) { + return requestForResponse(message, data); + } else { + Slog.w(TAG, "Failed to send message 0x" + Integer.toHexString(message)); + pending.completeExceptionally(new IllegalArgumentException( + "The message being sent must be either a one-way or a request." + )); + } + return pending; + } + + /** + * @deprecated Method was renamed to sendMessage(int, byte[]) to support both + * send-and-forget type messages as well as wait-for-response type messages. + * + * @param message request message type + * @param data the message payload + * @return future object containing the result of the request. + * + * @see #sendMessage(int, byte[]) + */ + @Deprecated public Future<byte[]> requestForResponse(int message, byte[] data) { if (DEBUG) Slog.d(TAG, "Requesting for response"); final int sequence = mNextSequence.incrementAndGet(); @@ -154,6 +197,20 @@ public abstract class Transport { return pending; } + private Future<byte[]> sendAndForget(int message, byte[]data) { + if (DEBUG) Slog.d(TAG, "Sending a one-way message"); + final CompletableFuture<byte[]> pending = new CompletableFuture<>(); + + try { + sendMessage(message, -1, data); + pending.complete(null); + } catch (IOException e) { + pending.completeExceptionally(e); + } + + return pending; + } + protected final void handleMessage(int message, int sequence, @NonNull byte[] data) throws IOException { if (DEBUG) { @@ -162,7 +219,9 @@ public abstract class Transport { + " from association " + mAssociationId); } - if (isRequest(message)) { + if (isOneway(message)) { + processOneway(message, data); + } else if (isRequest(message)) { try { processRequest(message, sequence, data); } catch (IOException e) { @@ -175,6 +234,21 @@ public abstract class Transport { } } + private void processOneway(int message, byte[] data) { + switch (message) { + case MESSAGE_ONEWAY_PING: + case MESSAGE_ONEWAY_FROM_WEARABLE: + case MESSAGE_ONEWAY_TO_WEARABLE: { + callback(message, data); + break; + } + default: { + Slog.w(TAG, "Ignoring unknown message 0x" + Integer.toHexString(message)); + break; + } + } + } + private void processRequest(int message, int sequence, byte[] data) throws IOException { switch (message) { diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING index a159a5e003c9..5a548fdca12e 100644 --- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING +++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING @@ -54,9 +54,7 @@ "exclude-annotation": "android.support.test.filters.FlakyTest" } ] - } - ], - "postsubmit": [ + }, { "name": "CtsVirtualDevicesCameraTestCases", "options": [ diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java index 6940ffe40a51..f24c4cc59336 100644 --- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java +++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright 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. @@ -70,7 +70,7 @@ public final class VirtualCameraConversionUtil { @Override public void onProcessCaptureRequest(int streamId, int frameId) throws RemoteException { - camera.onProcessCaptureRequest(streamId, frameId, /*metadata=*/ null); + camera.onProcessCaptureRequest(streamId, frameId); } @Override diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index ae0cd65b2770..00dd169e6cc4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -2238,6 +2238,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println("Performing idle maintenance..."); mInterface.sendIdleJobTrigger(); + mInternal.performIdleMaintenance(); return 0; } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index a6b532cdef09..2b81dbcb6fb2 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -2059,9 +2059,6 @@ class UserController implements Handler.Callback { mTargetUserId = targetUserId; userSwitchUiEnabled = mUserSwitchUiEnabled; } - if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) { - mInjector.setHasTopUi(true); - } if (userSwitchUiEnabled) { UserInfo currentUserInfo = getUserInfo(currentUserId); Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo); @@ -2130,9 +2127,6 @@ class UserController implements Handler.Callback { } private void endUserSwitch() { - if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) { - mInjector.setHasTopUi(false); - } final int nextUserId; synchronized (mLock) { nextUserId = ObjectUtils.getOrElse(mPendingTargetUserIds.poll(), UserHandle.USER_NULL); @@ -3050,8 +3044,8 @@ class UserController implements Handler.Callback { /** * Returns whether the given user requires credential entry at this time. This is used to - * intercept activity launches for locked work apps due to work challenge being triggered - * or when the profile user is yet to be unlocked. + * intercept activity launches for apps corresponding to locked profiles due to separate + * challenge being triggered or when the profile user is yet to be unlocked. */ protected boolean shouldConfirmCredentials(@UserIdInt int userId) { if (getStartedUserState(userId) == null) { @@ -3816,15 +3810,6 @@ class UserController implements Handler.Callback { getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId); } - void setHasTopUi(boolean hasTopUi) { - try { - Slogf.i(TAG, "Setting hasTopUi to " + hasTopUi); - mService.setHasTopUi(hasTopUi); - } catch (RemoteException e) { - Slogf.e(TAG, "Failed to allow using all CPU cores", e); - } - } - void onSystemUserVisibilityChanged(boolean visible) { getUserManagerInternal().onSystemUserVisibilityChanged(visible); } diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index b3fb9c947ca4..8b7e56ec410d 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -20,6 +20,7 @@ import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.EXTRA_REPLACING; import static android.server.app.Flags.gameDefaultFrameRate; +import static android.server.app.Flags.disableGameModeWhenAppTop; import static com.android.internal.R.styleable.GameModeConfig_allowGameAngleDriver; import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscaling; @@ -181,7 +182,9 @@ public final class GameManagerService extends IGameManagerService.Stub { @Nullable final MyUidObserver mUidObserver; @GuardedBy("mUidObserverLock") - private final Set<Integer> mForegroundGameUids = new HashSet<>(); + private final Set<Integer> mGameForegroundUids = new HashSet<>(); + @GuardedBy("mUidObserverLock") + private final Set<Integer> mNonGameForegroundUids = new HashSet<>(); private final GameManagerServiceSystemPropertiesWrapper mSysProps; private float mGameDefaultFrameRateValue; @@ -238,12 +241,10 @@ public final class GameManagerService extends IGameManagerService.Stub { FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP, -1, -1); - if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) { + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) { mGameServiceController = new GameServiceController( context, BackgroundThread.getExecutor(), - new GameServiceProviderSelectorImpl( - context.getResources(), - context.getPackageManager()), + new GameServiceProviderSelectorImpl(context.getResources(), mPackageManager), new GameServiceProviderInstanceFactoryImpl(context)); } else { mGameServiceController = null; @@ -2245,7 +2246,7 @@ public final class GameManagerService extends IGameManagerService.Stub { // Update all foreground games' frame rate. synchronized (mUidObserverLock) { - for (int uid : mForegroundGameUids) { + for (int uid : mGameForegroundUids) { setGameDefaultFrameRateOverride(uid, getGameDefaultFrameRate(isEnabled)); } } @@ -2261,31 +2262,44 @@ public final class GameManagerService extends IGameManagerService.Stub { @Override public void onUidGone(int uid, boolean disabled) { synchronized (mUidObserverLock) { - disableGameMode(uid); + handleUidMovedOffTop(uid); } } @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { - synchronized (mUidObserverLock) { - - if (procState != ActivityManager.PROCESS_STATE_TOP) { - disableGameMode(uid); + switch (procState) { + case ActivityManager.PROCESS_STATE_TOP: + handleUidMovedToTop(uid); return; - } + default: + handleUidMovedOffTop(uid); + } + } - final String[] packages = mContext.getPackageManager().getPackagesForUid(uid); - if (packages == null || packages.length == 0) { - return; - } + private void handleUidMovedToTop(int uid) { + final String[] packages = mPackageManager.getPackagesForUid(uid); + if (packages == null || packages.length == 0) { + return; + } - final int userId = mContext.getUserId(); - if (!Arrays.stream(packages).anyMatch(p -> isPackageGame(p, userId))) { + final int userId = mContext.getUserId(); + final boolean isNotGame = Arrays.stream(packages).noneMatch( + p -> isPackageGame(p, userId)); + synchronized (mUidObserverLock) { + if (isNotGame) { + if (disableGameModeWhenAppTop()) { + if (!mGameForegroundUids.isEmpty() && mNonGameForegroundUids.isEmpty()) { + Slog.v(TAG, "Game power mode OFF (first non-game in foreground)"); + mPowerManagerInternal.setPowerMode(Mode.GAME, false); + } + mNonGameForegroundUids.add(uid); + } return; } - - if (mForegroundGameUids.isEmpty()) { - Slog.v(TAG, "Game power mode ON (process state was changed to foreground)"); + if (mGameForegroundUids.isEmpty() && (!disableGameModeWhenAppTop() + || mNonGameForegroundUids.isEmpty())) { + Slog.v(TAG, "Game power mode ON (first game in foreground)"); mPowerManagerInternal.setPowerMode(Mode.GAME, true); } final boolean isGameDefaultFrameRateDisabled = @@ -2293,22 +2307,26 @@ public final class GameManagerService extends IGameManagerService.Stub { PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED, false); setGameDefaultFrameRateOverride(uid, getGameDefaultFrameRate(!isGameDefaultFrameRateDisabled)); - mForegroundGameUids.add(uid); + mGameForegroundUids.add(uid); } } - private void disableGameMode(int uid) { + private void handleUidMovedOffTop(int uid) { synchronized (mUidObserverLock) { - if (!mForegroundGameUids.contains(uid)) { - return; - } - mForegroundGameUids.remove(uid); - if (!mForegroundGameUids.isEmpty()) { - return; + if (mGameForegroundUids.contains(uid)) { + mGameForegroundUids.remove(uid); + if (mGameForegroundUids.isEmpty() && (!disableGameModeWhenAppTop() + || mNonGameForegroundUids.isEmpty())) { + Slog.v(TAG, "Game power mode OFF (no games in foreground)"); + mPowerManagerInternal.setPowerMode(Mode.GAME, false); + } + } else if (disableGameModeWhenAppTop() && mNonGameForegroundUids.contains(uid)) { + mNonGameForegroundUids.remove(uid); + if (mNonGameForegroundUids.isEmpty() && !mGameForegroundUids.isEmpty()) { + Slog.v(TAG, "Game power mode ON (only games in foreground)"); + mPowerManagerInternal.setPowerMode(Mode.GAME, true); + } } - Slog.v(TAG, - "Game power mode OFF (process remomved or state changed to background)"); - mPowerManagerInternal.setPowerMode(Mode.GAME, false); } } } diff --git a/services/core/java/com/android/server/app/flags.aconfig b/services/core/java/com/android/server/app/flags.aconfig index f2e4783bd9eb..0673013bdc44 100644 --- a/services/core/java/com/android/server/app/flags.aconfig +++ b/services/core/java/com/android/server/app/flags.aconfig @@ -6,4 +6,11 @@ flag { description: "This flag guards the new behavior with the addition of Game Default Frame Rate feature." bug: "286084594" is_fixed_read_only: true -}
\ No newline at end of file +} + +flag { + name: "disable_game_mode_when_app_top" + namespace: "game" + description: "Disable game power mode when a non-game app is also top and visible" + bug: "299295925" +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 56ae2bfcb38f..ea791b775125 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -33,6 +33,7 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static android.media.AudioManager.STREAM_SYSTEM; +import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.INVALID_UID; import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; @@ -116,6 +117,7 @@ import android.media.AudioRoutesInfo; import android.media.AudioSystem; import android.media.AudioTrack; import android.media.BluetoothProfileConnectionInfo; +import android.media.FadeManagerConfiguration; import android.media.IAudioDeviceVolumeDispatcher; import android.media.IAudioFocusDispatcher; import android.media.IAudioModeDispatcher; @@ -4513,6 +4515,8 @@ public class AudioService extends IAudioService.Stub + bluetoothMacAddressAnonymization()); pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:" + disablePrescaleAbsoluteVolume()); + pw.println("\tandroid.media.audiopolicy.enableFadeManagerConfiguration:" + + enableFadeManagerConfiguration()); } private void dumpAudioMode(PrintWriter pw) { @@ -12614,6 +12618,47 @@ public class AudioService extends IAudioService.Stub } /** + * see {@link AudioPolicy#setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)} + */ + @android.annotation.EnforcePermission( + android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public int setFadeManagerConfigurationForFocusLoss( + @NonNull FadeManagerConfiguration fmcForFocusLoss) { + super.setFadeManagerConfigurationForFocusLoss_enforcePermission(); + ensureFadeManagerConfigIsEnabled(); + Objects.requireNonNull(fmcForFocusLoss, + "Fade manager config for focus loss cannot be null"); + validateFadeManagerConfiguration(fmcForFocusLoss); + + return mPlaybackMonitor.setFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS, + fmcForFocusLoss); + } + + /** + * see {@link AudioPolicy#clearFadeManagerConfigurationForFocusLoss()} + */ + @android.annotation.EnforcePermission( + android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public int clearFadeManagerConfigurationForFocusLoss() { + super.clearFadeManagerConfigurationForFocusLoss_enforcePermission(); + ensureFadeManagerConfigIsEnabled(); + + return mPlaybackMonitor.clearFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS); + } + + /** + * see {@link AudioPolicy#getFadeManagerConfigurationForFocusLoss()} + */ + @android.annotation.EnforcePermission( + android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() { + super.getFadeManagerConfigurationForFocusLoss_enforcePermission(); + ensureFadeManagerConfigIsEnabled(); + + return mPlaybackMonitor.getFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS); + } + + /** * @see AudioManager#getHalVersion */ public @Nullable AudioHalVersionInfo getHalVersion() { @@ -12814,6 +12859,19 @@ public class AudioService extends IAudioService.Stub } } + private void ensureFadeManagerConfigIsEnabled() { + Preconditions.checkState(enableFadeManagerConfiguration(), + "Fade manager configuration not supported"); + } + + private void validateFadeManagerConfiguration(FadeManagerConfiguration fmc) { + // validate permission of audio attributes + List<AudioAttributes> attrs = fmc.getAudioAttributesWithVolumeShaperConfigs(); + for (int index = 0; index < attrs.size(); index++) { + validateAudioAttributesUsage(attrs.get(index)); + } + } + //====================== // Audio policy callbacks from AudioSystem for dynamic policies //====================== @@ -13114,6 +13172,7 @@ public class AudioService extends IAudioService.Stub + "could not link to " + projection + " binder death", e); } } + int status = connectMixes(); if (status != AudioSystem.SUCCESS) { release(); @@ -13471,6 +13530,43 @@ public class AudioService extends IAudioService.Stub } } + /** + * see {@link AudioManager#dispatchAudioFocusChangeWithFade(AudioFocusInfo, int, AudioPolicy, + * List, FadeManagerConfiguration)} + */ + @android.annotation.EnforcePermission( + android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public int dispatchFocusChangeWithFade(AudioFocusInfo afi, int focusChange, + IAudioPolicyCallback pcb, List<AudioFocusInfo> otherActiveAfis, + FadeManagerConfiguration transientFadeMgrConfig) { + super.dispatchFocusChangeWithFade_enforcePermission(); + ensureFadeManagerConfigIsEnabled(); + Objects.requireNonNull(afi, "AudioFocusInfo cannot be null"); + Objects.requireNonNull(pcb, "AudioPolicy callback cannot be null"); + Objects.requireNonNull(otherActiveAfis, + "Other active AudioFocusInfo list cannot be null"); + if (transientFadeMgrConfig != null) { + validateFadeManagerConfiguration(transientFadeMgrConfig); + } + + synchronized (mAudioPolicies) { + Preconditions.checkState(mAudioPolicies.containsKey(pcb.asBinder()), + "Unregistered AudioPolicy for focus dispatch with fade"); + + // set the transient fade manager config to be used for handling this focus change + if (transientFadeMgrConfig != null) { + mPlaybackMonitor.setTransientFadeManagerConfiguration(focusChange, + transientFadeMgrConfig); + } + int status = mMediaFocusControl.dispatchFocusChangeWithFade(afi, focusChange, + otherActiveAfis); + + if (transientFadeMgrConfig != null) { + mPlaybackMonitor.clearTransientFadeManagerConfiguration(focusChange); + } + return status; + } + } //====================== // Audioserver state dispatch diff --git a/services/core/java/com/android/server/audio/FadeConfigurations.java b/services/core/java/com/android/server/audio/FadeConfigurations.java index 2e27c7697e82..37ecf0bdcc7a 100644 --- a/services/core/java/com/android/server/audio/FadeConfigurations.java +++ b/services/core/java/com/android/server/audio/FadeConfigurations.java @@ -16,13 +16,22 @@ package com.android.server.audio; +import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.media.AudioAttributes; +import android.media.AudioManager; import android.media.AudioPlaybackConfiguration; +import android.media.FadeManagerConfiguration; import android.media.VolumeShaper; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; + +import java.util.Collections; import java.util.List; +import java.util.Objects; /** * Class to encapsulate configurations used for fading players @@ -69,51 +78,229 @@ public final class FadeConfigurations { private static final int INVALID_UID = -1; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private FadeManagerConfiguration mDefaultFadeManagerConfig; + @GuardedBy("mLock") + private FadeManagerConfiguration mUpdatedFadeManagerConfig; + @GuardedBy("mLock") + private FadeManagerConfiguration mTransientFadeManagerConfig; + /** active fade manager is one of: transient > updated > default */ + @GuardedBy("mLock") + private FadeManagerConfiguration mActiveFadeManagerConfig; + + /** + * Sets the custom fade manager configuration + * + * @param fadeManagerConfig custom fade manager configuration + * @return {@link AudioManager#SUCCESS} if setting custom fade manager configuration succeeds + * or {@link AudioManager#ERROR} otherwise (example - when fade manager configuration + * feature is disabled) + */ + public int setFadeManagerConfiguration( + @NonNull FadeManagerConfiguration fadeManagerConfig) { + if (!enableFadeManagerConfiguration()) { + return AudioManager.ERROR; + } + + synchronized (mLock) { + mUpdatedFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig, + "Fade manager configuration cannot be null"); + mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked(); + } + return AudioManager.SUCCESS; + } + + /** + * Clears the fade manager configuration that was previously set with + * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)} + * + * @return {@link AudioManager#SUCCESS} if previously set fade manager configuration is cleared + * or {@link AudioManager#ERROR} otherwise (example, when fade manager configuration feature + * is disabled) + */ + public int clearFadeManagerConfiguration() { + if (!enableFadeManagerConfiguration()) { + return AudioManager.ERROR; + } + + synchronized (mLock) { + mUpdatedFadeManagerConfig = null; + mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked(); + } + return AudioManager.SUCCESS; + } + + /** + * Returns the active fade manager configuration + * + * @return {@code null} if feature is disabled, or the custom fade manager configuration if set, + * or default fade manager configuration if not set. + */ + @Nullable + public FadeManagerConfiguration getFadeManagerConfiguration() { + if (!enableFadeManagerConfiguration()) { + return null; + } + + synchronized (mLock) { + return mActiveFadeManagerConfig; + } + } + + /** + * Sets the transient fade manager configuration + * + * @param fadeManagerConfig custom fade manager configuration + * @return {@link AudioManager#SUCCESS} if setting custom fade manager configuration succeeds + * or {@link AudioManager#ERROR} otherwise (example - when fade manager configuration is + * disabled) + */ + public int setTransientFadeManagerConfiguration( + @NonNull FadeManagerConfiguration fadeManagerConfig) { + if (!enableFadeManagerConfiguration()) { + return AudioManager.ERROR; + } + + synchronized (mLock) { + mTransientFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig, + "Transient FadeManagerConfiguration cannot be null"); + mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked(); + } + return AudioManager.SUCCESS; + } + + /** + * Clears the transient fade manager configuration that was previously set with + * {@link #setTransientFadeManagerConfiguration(FadeManagerConfiguration)} + * + * @return {@link AudioManager#SUCCESS} if previously set transient fade manager configuration + * is cleared or {@link AudioManager#ERROR} otherwise (example - when fade manager + * configuration is disabled) + */ + public int clearTransientFadeManagerConfiguration() { + if (!enableFadeManagerConfiguration()) { + return AudioManager.ERROR; + } + synchronized (mLock) { + mTransientFadeManagerConfig = null; + mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked(); + } + return AudioManager.SUCCESS; + } + + /** + * Query if fade should be enforecd on players + * + * @return {@code true} if fade is enabled or using default configurations, {@code false} + * otherwise. + */ + public boolean isFadeEnabled() { + if (!enableFadeManagerConfiguration()) { + return true; + } + + synchronized (mLock) { + return getUpdatedFadeManagerConfigLocked().isFadeEnabled(); + } + } + /** * Query {@link android.media.AudioAttributes.AttributeUsage usages} that are allowed to * fade + * * @return list of {@link android.media.AudioAttributes.AttributeUsage} */ @NonNull public List<Integer> getFadeableUsages() { - return DEFAULT_FADEABLE_USAGES; + if (!enableFadeManagerConfiguration()) { + return DEFAULT_FADEABLE_USAGES; + } + + synchronized (mLock) { + FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked(); + // when fade is not enabled, return an empty list instead + return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getFadeableUsages() + : Collections.EMPTY_LIST; + } } /** * Query {@link android.media.AudioAttributes.AttributeContentType content types} that are * exempted from fade enforcement + * * @return list of {@link android.media.AudioAttributes.AttributeContentType} */ @NonNull public List<Integer> getUnfadeableContentTypes() { - return DEFAULT_UNFADEABLE_CONTENT_TYPES; + if (!enableFadeManagerConfiguration()) { + return DEFAULT_UNFADEABLE_CONTENT_TYPES; + } + + synchronized (mLock) { + FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked(); + // when fade is not enabled, return an empty list instead + return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableContentTypes() + : Collections.EMPTY_LIST; + } } /** * Query {@link android.media.AudioPlaybackConfiguration.PlayerType player types} that are * exempted from fade enforcement + * * @return list of {@link android.media.AudioPlaybackConfiguration.PlayerType} */ @NonNull public List<Integer> getUnfadeablePlayerTypes() { - return DEFAULT_UNFADEABLE_PLAYER_TYPES; + if (!enableFadeManagerConfiguration()) { + return DEFAULT_UNFADEABLE_PLAYER_TYPES; + } + + synchronized (mLock) { + FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked(); + // when fade is not enabled, return an empty list instead + return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeablePlayerTypes() + : Collections.EMPTY_LIST; + } } /** * Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied * for the fade-out + * * @param aa The {@link android.media.AudioAttributes} * @return {@link android.media.VolumeShaper.Configuration} for the - * {@link android.media.AudioAttributes.AttributeUsage} or default volume shaper if not - * configured + * {@link android.media.AudioAttributes} or default volume shaper if not configured */ @NonNull public VolumeShaper.Configuration getFadeOutVolumeShaperConfig(@NonNull AudioAttributes aa) { - return DEFAULT_FADEOUT_VSHAPE; + if (!enableFadeManagerConfiguration()) { + return DEFAULT_FADEOUT_VSHAPE; + } + return getOptimalFadeOutVolShaperConfig(aa); + } + + /** + * Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied for the + * fade in + * + * @param aa The {@link android.media.AudioAttributes} + * @return {@link android.media.VolumeShaper.Configuration} for the + * {@link android.media.AudioAttributes} or {@code null} otherwise + */ + @Nullable + public VolumeShaper.Configuration getFadeInVolumeShaperConfig(@NonNull AudioAttributes aa) { + if (!enableFadeManagerConfiguration()) { + return null; + } + return getOptimalFadeInVolShaperConfig(aa); } + /** * Get the duration to fade out a player of type usage + * * @param aa The {@link android.media.AudioAttributes} * @return duration in milliseconds for the * {@link android.media.AudioAttributes} or default duration if not configured @@ -122,22 +309,73 @@ public final class FadeConfigurations { if (!isFadeable(aa, INVALID_UID, AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN)) { return 0; } - return DEFAULT_FADE_OUT_DURATION_MS; + if (!enableFadeManagerConfiguration()) { + return DEFAULT_FADE_OUT_DURATION_MS; + } + return getOptimalFadeOutDuration(aa); } /** - * Get the delay to fade in offending players that do not stop after losing audio focus. + * Get the delay to fade in offending players that do not stop after losing audio focus + * * @param aa The {@link android.media.AudioAttributes} * @return delay in milliseconds for the * {@link android.media.AudioAttributes.Attribute} or default delay if not configured */ public long getDelayFadeInOffenders(@NonNull AudioAttributes aa) { - return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS; + if (!enableFadeManagerConfiguration()) { + return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS; + } + + synchronized (mLock) { + return getUpdatedFadeManagerConfigLocked().getFadeInDelayForOffenders(); + } + } + + /** + * Query {@link android.media.AudioAttributes} that are exempted from fade enforcement + * + * @return list of {@link android.media.AudioAttributes} + */ + @NonNull + public List<AudioAttributes> getUnfadeableAudioAttributes() { + // unfadeable audio attributes is only supported with fade manager configurations + if (!enableFadeManagerConfiguration()) { + return Collections.EMPTY_LIST; + } + + synchronized (mLock) { + FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked(); + // when fade is not enabled, return empty list + return fadeManagerConfig.isFadeEnabled() + ? fadeManagerConfig.getUnfadeableAudioAttributes() : Collections.EMPTY_LIST; + } + } + + /** + * Query uids that are exempted from fade enforcement + * + * @return list of uids + */ + @NonNull + public List<Integer> getUnfadeableUids() { + // unfadeable uids is only supported with fade manager configurations + if (!enableFadeManagerConfiguration()) { + return Collections.EMPTY_LIST; + } + + synchronized (mLock) { + FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked(); + // when fade is not enabled, return empty list + return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableUids() + : Collections.EMPTY_LIST; + } } /** * Check if it is allowed to fade for the given {@link android.media.AudioAttributes}, - * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config. + * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config + * * @param aa The {@link android.media.AudioAttributes} * @param uid The uid of the client owning the player * @param playerType The {@link android.media.AudioPlaybackConfiguration.PlayerType} @@ -145,36 +383,173 @@ public final class FadeConfigurations { */ public boolean isFadeable(@NonNull AudioAttributes aa, int uid, @AudioPlaybackConfiguration.PlayerType int playerType) { - if (isPlayerTypeUnfadeable(playerType)) { - if (DEBUG) { - Slog.i(TAG, "not fadeable: player type:" + playerType); + synchronized (mLock) { + if (isPlayerTypeUnfadeableLocked(playerType)) { + if (DEBUG) { + Slog.i(TAG, "not fadeable: player type:" + playerType); + } + return false; } - return false; + if (isContentTypeUnfadeableLocked(aa.getContentType())) { + if (DEBUG) { + Slog.i(TAG, "not fadeable: content type:" + aa.getContentType()); + } + return false; + } + if (!isUsageFadeableLocked(aa.getSystemUsage())) { + if (DEBUG) { + Slog.i(TAG, "not fadeable: usage:" + aa.getUsage()); + } + return false; + } + // new configs using fade manager configuration + if (isUnfadeableForFadeMgrConfigLocked(aa, uid)) { + return false; + } + return true; + } + } + + /** Tries to get the fade out volume shaper config closest to the audio attributes */ + private VolumeShaper.Configuration getOptimalFadeOutVolShaperConfig(AudioAttributes aa) { + synchronized (mLock) { + FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked(); + // check if the specific audio attributes has a volume shaper config defined + VolumeShaper.Configuration volShaperConfig = + fadeManagerConfig.getFadeOutVolumeShaperConfigForAudioAttributes(aa); + if (volShaperConfig != null) { + return volShaperConfig; + } + + // get the volume shaper config for usage + // for fadeable usages, this should never return null + return fadeManagerConfig.getFadeOutVolumeShaperConfigForUsage( + aa.getSystemUsage()); } - if (isContentTypeUnfadeable(aa.getContentType())) { + } + + /** Tries to get the fade in volume shaper config closest to the audio attributes */ + private VolumeShaper.Configuration getOptimalFadeInVolShaperConfig(AudioAttributes aa) { + synchronized (mLock) { + FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked(); + // check if the specific audio attributes has a volume shaper config defined + VolumeShaper.Configuration volShaperConfig = + fadeManagerConfig.getFadeInVolumeShaperConfigForAudioAttributes(aa); + if (volShaperConfig != null) { + return volShaperConfig; + } + + // get the volume shaper config for usage + // for fadeable usages, this should never return null + return fadeManagerConfig.getFadeInVolumeShaperConfigForUsage(aa.getSystemUsage()); + } + } + + /** Tries to get the duration closest to the audio attributes */ + private long getOptimalFadeOutDuration(AudioAttributes aa) { + synchronized (mLock) { + FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked(); + // check if specific audio attributes has a duration defined + long duration = fadeManagerConfig.getFadeOutDurationForAudioAttributes(aa); + if (duration != FadeManagerConfiguration.DURATION_NOT_SET) { + return duration; + } + + // get the duration for usage + // for fadeable usages, this should never return DURATION_NOT_SET + return fadeManagerConfig.getFadeOutDurationForUsage(aa.getSystemUsage()); + } + } + + @GuardedBy("mLock") + private boolean isUnfadeableForFadeMgrConfigLocked(AudioAttributes aa, int uid) { + if (isAudioAttributesUnfadeableLocked(aa)) { if (DEBUG) { - Slog.i(TAG, "not fadeable: content type:" + aa.getContentType()); + Slog.i(TAG, "not fadeable: aa:" + aa); } - return false; + return true; } - if (!isUsageFadeable(aa.getUsage())) { + if (isUidUnfadeableLocked(uid)) { if (DEBUG) { - Slog.i(TAG, "not fadeable: usage:" + aa.getUsage()); + Slog.i(TAG, "not fadeable: uid:" + uid); } + return true; + } + return false; + } + + @GuardedBy("mLock") + private boolean isUsageFadeableLocked(int usage) { + if (!enableFadeManagerConfiguration()) { + return DEFAULT_FADEABLE_USAGES.contains(usage); + } + return getUpdatedFadeManagerConfigLocked().isUsageFadeable(usage); + } + + @GuardedBy("mLock") + private boolean isContentTypeUnfadeableLocked(int contentType) { + if (!enableFadeManagerConfiguration()) { + return DEFAULT_UNFADEABLE_CONTENT_TYPES.contains(contentType); + } + return getUpdatedFadeManagerConfigLocked().isContentTypeUnfadeable(contentType); + } + + @GuardedBy("mLock") + private boolean isPlayerTypeUnfadeableLocked(int playerType) { + if (!enableFadeManagerConfiguration()) { + return DEFAULT_UNFADEABLE_PLAYER_TYPES.contains(playerType); + } + return getUpdatedFadeManagerConfigLocked().isPlayerTypeUnfadeable(playerType); + } + + @GuardedBy("mLock") + private boolean isAudioAttributesUnfadeableLocked(AudioAttributes aa) { + if (!enableFadeManagerConfiguration()) { + // default fade configs do not support unfadeable audio attributes, hence return false return false; } - return true; + return getUpdatedFadeManagerConfigLocked().isAudioAttributesUnfadeable(aa); } - private boolean isUsageFadeable(int usage) { - return getFadeableUsages().contains(usage); + @GuardedBy("mLock") + private boolean isUidUnfadeableLocked(int uid) { + if (!enableFadeManagerConfiguration()) { + // default fade configs do not support unfadeable uids, hence return false + return false; + } + return getUpdatedFadeManagerConfigLocked().isUidUnfadeable(uid); } - private boolean isContentTypeUnfadeable(int contentType) { - return getUnfadeableContentTypes().contains(contentType); + @GuardedBy("mLock") + private FadeManagerConfiguration getUpdatedFadeManagerConfigLocked() { + if (mActiveFadeManagerConfig == null) { + mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked(); + } + return mActiveFadeManagerConfig; } - private boolean isPlayerTypeUnfadeable(int playerType) { - return getUnfadeablePlayerTypes().contains(playerType); + /** Priority between fade manager configs: Transient > Updated > Default */ + @GuardedBy("mLock") + private FadeManagerConfiguration getActiveFadeMgrConfigLocked() { + // below configs are arranged in the order of priority + // configs placed higher have higher priority + if (mTransientFadeManagerConfig != null) { + return mTransientFadeManagerConfig; + } + + if (mUpdatedFadeManagerConfig != null) { + return mUpdatedFadeManagerConfig; + } + + // default - must be the lowest priority + return getDefaultFadeManagerConfigLocked(); + } + + @GuardedBy("mLock") + private FadeManagerConfiguration getDefaultFadeManagerConfigLocked() { + if (mDefaultFadeManagerConfig == null) { + mDefaultFadeManagerConfig = new FadeManagerConfiguration.Builder().build(); + } + return mDefaultFadeManagerConfig; } } diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java index 1171f97533c7..2cceb5ab5d2e 100644 --- a/services/core/java/com/android/server/audio/FadeOutManager.java +++ b/services/core/java/com/android/server/audio/FadeOutManager.java @@ -16,21 +16,24 @@ package com.android.server.audio; +import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioPlaybackConfiguration; +import android.media.FadeManagerConfiguration; import android.media.VolumeShaper; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.server.utils.EventLogger; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Objects; +import java.util.List; +import java.util.Map; /** * Class to handle fading out players @@ -40,14 +43,6 @@ public final class FadeOutManager { public static final String TAG = "AS.FadeOutManager"; private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG; - private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED = - new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY) - .createIfNeeded() - .build(); - - // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp - private static final VolumeShaper.Operation PLAY_SKIP_RAMP = - new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build(); private final Object mLock = new Object(); @@ -57,16 +52,81 @@ public final class FadeOutManager { @GuardedBy("mLock") private final SparseArray<FadedOutApp> mUidToFadedAppsMap = new SparseArray<>(); - @GuardedBy("mLock") - private FadeConfigurations mFadeConfigurations; + private final FadeConfigurations mFadeConfigurations = new FadeConfigurations(); + + /** + * Sets the custom fade manager configuration to be used for player fade out and in + * + * @param fadeManagerConfig custom fade manager configuration + * @return {@link AudioManager#SUCCESS} if setting fade manager config succeeded, + * {@link AudioManager#ERROR} otherwise + */ + int setFadeManagerConfiguration(FadeManagerConfiguration fadeManagerConfig) { + // locked to ensure the fade configs are not updated while faded app state is being updated + synchronized (mLock) { + return mFadeConfigurations.setFadeManagerConfiguration(fadeManagerConfig); + } + } - public FadeOutManager() { - mFadeConfigurations = new FadeConfigurations(); + /** + * Clears the fade manager configuration that was previously set with + * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)} + * + * @return {@link AudioManager#SUCCESS} if clearing fade manager config succeeded, + * {@link AudioManager#ERROR} otherwise + */ + int clearFadeManagerConfiguration() { + // locked to ensure the fade configs are not updated while faded app state is being updated + synchronized (mLock) { + return mFadeConfigurations.clearFadeManagerConfiguration(); + } } - public FadeOutManager(FadeConfigurations fadeConfigurations) { - mFadeConfigurations = Objects.requireNonNull(fadeConfigurations, - "Fade configurations can not be null"); + /** + * Returns the active fade manager configuration + * + * @return the {@link FadeManagerConfiguration} + */ + FadeManagerConfiguration getFadeManagerConfiguration() { + return mFadeConfigurations.getFadeManagerConfiguration(); + } + + /** + * Sets the transient fade manager configuration to be used for player fade out and in + * + * @param fadeManagerConfig fade manager config that has higher priority than the existing + * fade manager configuration. This is expected to be transient. + * @return {@link AudioManager#SUCCESS} if setting fade manager config succeeded, + * {@link AudioManager#ERROR} otherwise + */ + int setTransientFadeManagerConfiguration(FadeManagerConfiguration fadeManagerConfig) { + // locked to ensure the fade configs are not updated while faded app state is being updated + synchronized (mLock) { + return mFadeConfigurations.setTransientFadeManagerConfiguration(fadeManagerConfig); + } + } + + /** + * Clears the transient fade manager configuration that was previously set with + * {@link #setTransientFadeManagerConfiguration(FadeManagerConfiguration)} + * + * @return {@link AudioManager#SUCCESS} if clearing fade manager config succeeded, + * {@link AudioManager#ERROR} otherwise + */ + int clearTransientFadeManagerConfiguration() { + // locked to ensure the fade configs are not updated while faded app state is being updated + synchronized (mLock) { + return mFadeConfigurations.clearTransientFadeManagerConfiguration(); + } + } + + /** + * Query if fade is enblead and can be enforced on players + * + * @return {@code true} if fade is enabled, {@code false} otherwise. + */ + boolean isFadeEnabled() { + return mFadeConfigurations.isFadeEnabled(); } // TODO explore whether a shorter fade out would be a better UX instead of not fading out at all @@ -128,7 +188,7 @@ public final class FadeOutManager { } } - void fadeOutUid(int uid, ArrayList<AudioPlaybackConfiguration> players) { + void fadeOutUid(int uid, List<AudioPlaybackConfiguration> players) { Slog.i(TAG, "fadeOutUid() uid:" + uid); synchronized (mLock) { if (!mUidToFadedAppsMap.contains(uid)) { @@ -148,15 +208,31 @@ public final class FadeOutManager { * @param uid the uid for the app to unfade out * @param players map of current available players (so we can get an APC from piid) */ - void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) { + void unfadeOutUid(int uid, Map<Integer, AudioPlaybackConfiguration> players) { Slog.i(TAG, "unfadeOutUid() uid:" + uid); synchronized (mLock) { - final FadedOutApp fa = mUidToFadedAppsMap.get(uid); + FadedOutApp fa = mUidToFadedAppsMap.get(uid); if (fa == null) { return; } mUidToFadedAppsMap.remove(uid); - fa.removeUnfadeAll(players); + + if (!enableFadeManagerConfiguration()) { + fa.removeUnfadeAll(players); + return; + } + + // since fade manager configs may have volume-shaper config per audio attributes, + // iterate through each palyer and gather respective configs for fade in + ArrayList<AudioPlaybackConfiguration> apcs = new ArrayList<>(players.values()); + for (int index = 0; index < apcs.size(); index++) { + AudioPlaybackConfiguration apc = apcs.get(index); + VolumeShaper.Configuration config = mFadeConfigurations + .getFadeInVolumeShaperConfig(apc.getAudioAttributes()); + fa.fadeInPlayer(apc, config); + } + // ideal case all players should be faded in + fa.clear(); } } @@ -209,16 +285,6 @@ public final class FadeOutManager { } } - /** - * Update fade configurations used for player fade operations - * @param fadeConfigurations set of configs that define fade properties - */ - void setFadeConfigurations(@NonNull FadeConfigurations fadeConfigurations) { - synchronized (mLock) { - mFadeConfigurations = fadeConfigurations; - } - } - void dump(PrintWriter pw) { synchronized (mLock) { for (int index = 0; index < mUidToFadedAppsMap.size(); index++) { @@ -232,6 +298,15 @@ public final class FadeOutManager { * Class to group players from a common app, that are faded out. */ private static final class FadedOutApp { + private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED = + new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY) + .createIfNeeded() + .build(); + + // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp + private static final VolumeShaper.Operation PLAY_SKIP_RAMP = + new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build(); + private final int mUid; // key -> piid; value -> volume shaper config applied private final SparseArray<VolumeShaper.Configuration> mFadedPlayers = new SparseArray<>(); @@ -269,17 +344,8 @@ public final class FadeOutManager { return; } if (apc.getPlayerProxy() != null) { - try { - PlaybackActivityMonitor.sEventLogger.enqueue( - (new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp)).printLog( - TAG)); - apc.getPlayerProxy().applyVolumeShaper(volShaper, - skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED); - mFadedPlayers.put(piid, volShaper); - } catch (Exception e) { - Slog.e(TAG, "Error fading out player piid:" + piid - + " uid:" + apc.getClientUid(), e); - } + applyVolumeShaperInternal(apc, piid, volShaper, + skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED); } else { if (DEBUG) { Slog.v(TAG, "Error fading out player piid:" + piid @@ -288,21 +354,13 @@ public final class FadeOutManager { } } - void removeUnfadeAll(HashMap<Integer, AudioPlaybackConfiguration> players) { + void removeUnfadeAll(Map<Integer, AudioPlaybackConfiguration> players) { for (int index = 0; index < mFadedPlayers.size(); index++) { int piid = mFadedPlayers.keyAt(index); final AudioPlaybackConfiguration apc = players.get(piid); if ((apc != null) && (apc.getPlayerProxy() != null)) { - final VolumeShaper.Configuration volShaper = mFadedPlayers.valueAt(index); - try { - PlaybackActivityMonitor.sEventLogger.enqueue( - (new EventLogger.StringEvent("unfading out piid:" - + piid)).printLog(TAG)); - apc.getPlayerProxy().applyVolumeShaper(volShaper, - VolumeShaper.Operation.REVERSE); - } catch (Exception e) { - Slog.e(TAG, "Error unfading out player piid:" + piid + " uid:" + mUid, e); - } + applyVolumeShaperInternal(apc, piid, /* volShaperConfig= */ null, + VolumeShaper.Operation.REVERSE); } else { // this piid was in the list of faded players, but wasn't found if (DEBUG) { @@ -314,8 +372,61 @@ public final class FadeOutManager { mFadedPlayers.clear(); } + void fadeInPlayer(@NonNull AudioPlaybackConfiguration apc, + @Nullable VolumeShaper.Configuration config) { + int piid = Integer.valueOf(apc.getPlayerInterfaceId()); + // if not found, no need to fade in since it was never faded out + if (!mFadedPlayers.contains(piid)) { + if (DEBUG) { + Slog.v(TAG, "Player (piid: " + piid + ") for uid (" + mUid + + ") is not faded out, no need to fade in"); + } + return; + } + + mFadedPlayers.remove(piid); + if (apc.getPlayerProxy() != null) { + applyVolumeShaperInternal(apc, piid, config, + config != null ? PLAY_CREATE_IF_NEEDED : VolumeShaper.Operation.REVERSE); + } else { + if (DEBUG) { + Slog.v(TAG, "Error fading in player piid:" + piid + + ", player not found for uid " + mUid); + } + } + } + + void clear() { + if (mFadedPlayers.size() > 0) { + if (DEBUG) { + Slog.v(TAG, "Non empty faded players list being cleared! Faded out players:" + + mFadedPlayers); + } + } + // should the players be faded in irrespective? + mFadedPlayers.clear(); + } + void removeReleased(@NonNull AudioPlaybackConfiguration apc) { mFadedPlayers.delete(Integer.valueOf(apc.getPlayerInterfaceId())); } + + private void applyVolumeShaperInternal(AudioPlaybackConfiguration apc, int piid, + VolumeShaper.Configuration volShaperConfig, VolumeShaper.Operation operation) { + VolumeShaper.Configuration config = volShaperConfig; + // when operation is reverse, use the fade out volume shaper config instead + if (operation.equals(VolumeShaper.Operation.REVERSE)) { + config = mFadedPlayers.get(piid); + } + try { + PlaybackActivityMonitor.sEventLogger.enqueue( + (new PlaybackActivityMonitor.FadeEvent(apc, config, operation)) + .printLog(TAG)); + apc.getPlayerProxy().applyVolumeShaper(config, operation); + } catch (Exception e) { + Slog.e(TAG, "Error fading player piid:" + piid + " uid:" + mUid + + " operation:" + operation, e); + } + } } } diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java index 00c04ff12c89..f462539d5bbf 100644 --- a/services/core/java/com/android/server/audio/FocusRequester.java +++ b/services/core/java/com/android/server/audio/FocusRequester.java @@ -33,6 +33,7 @@ import com.android.server.audio.MediaFocusControl.AudioFocusDeathHandler; import com.android.server.pm.UserManagerInternal; import java.io.PrintWriter; +import java.util.List; /** * @hide @@ -534,6 +535,33 @@ public class FocusRequester { return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } + @GuardedBy("MediaFocusControl.mAudioFocusLock") + int dispatchFocusChangeWithFadeLocked(int focusChange, List<FocusRequester> otherActiveFrs) { + if (focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK + || focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE + || focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT + || focusChange == AudioManager.AUDIOFOCUS_GAIN) { + mFocusLossFadeLimbo = false; + mFocusController.restoreVShapedPlayers(this); + } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS + && mFocusController.shouldEnforceFade()) { + for (int index = 0; index < otherActiveFrs.size(); index++) { + // candidate for fade-out before a receiving a loss + if (mFocusController.fadeOutPlayers(otherActiveFrs.get(index), /* loser= */ this)) { + // active players are being faded out, delay the dispatch of focus loss + // mark this instance as being faded so it's not released yet as the focus loss + // will be dispatched later, it is now in limbo mode + mFocusLossFadeLimbo = true; + mFocusController.postDelayedLossAfterFade(this, + mFocusController.getFadeOutDurationOnFocusLossMillis( + this.getAudioAttributes())); + return AudioManager.AUDIOFOCUS_REQUEST_DELAYED; + } + } + } + return dispatchFocusChange(focusChange); + } + void dispatchFocusResultFromExtPolicy(int requestResult) { final IAudioFocusDispatcher fd = mFocusDispatcher; if (fd == null) { diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 58f5d5e21cf0..0df0006c7be3 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -16,6 +16,8 @@ package com.android.server.audio; +import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; @@ -195,6 +197,15 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } return mFocusEnforcer.getFadeInDelayForOffendersMillis(aa); } + + @Override + public boolean shouldEnforceFade() { + if (!enableFadeManagerConfiguration()) { + return ENFORCE_FADEOUT_FOR_FOCUS_LOSS; + } + + return mFocusEnforcer.shouldEnforceFade(); + } //========================================================================================== // AudioFocus //========================================================================================== @@ -861,14 +872,17 @@ public class MediaFocusControl implements PlayerFocusEnforcer { return; } } - final FocusRequester fr; - if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { - fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId()); - } else { - fr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); - } - if (fr != null) { - fr.dispatchFocusResultFromExtPolicy(requestResult); + synchronized (mAudioFocusLock) { + FocusRequester fr = getFocusRequesterLocked(afi.getClientId(), + /* shouldRemove= */ requestResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED); + if (fr != null) { + fr.dispatchFocusResultFromExtPolicy(requestResult); + // if fade is enabled for external focus policies, apply it when setting + // focus result as well + if (enableFadeManagerConfiguration()) { + fr.handleFocusGainFromRequest(requestResult); + } + } } } @@ -902,22 +916,78 @@ public class MediaFocusControl implements PlayerFocusEnforcer { + afi.getClientId()); } synchronized (mAudioFocusLock) { - if (mFocusPolicy == null) { - if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); } + FocusRequester fr = getFocusRequesterLocked(afi.getClientId(), + /* shouldRemove= */ focusChange == AudioManager.AUDIOFOCUS_LOSS); + if (fr == null) { + if (DEBUG) { + Log.v(TAG, "> failed: no such focus requester known"); + } return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } - final FocusRequester fr; - if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { - fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId()); - } else { - fr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); - } + return fr.dispatchFocusChange(focusChange); + } + } + + int dispatchFocusChangeWithFade(AudioFocusInfo afi, int focusChange, + List<AudioFocusInfo> otherActiveAfis) { + if (DEBUG) { + Log.v(TAG, "dispatchFocusChangeWithFade " + AudioManager.audioFocusToString(focusChange) + + " to afi client=" + afi.getClientId() + + " other active afis=" + otherActiveAfis); + } + + synchronized (mAudioFocusLock) { + String clientId = afi.getClientId(); + // do not remove the entry since it can be posted for fade + FocusRequester fr = getFocusRequesterLocked(clientId, /* shouldRemove= */ false); if (fr == null) { - if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); } + if (DEBUG) { + Log.v(TAG, "> failed: no such focus requester known"); + } return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } - return fr.dispatchFocusChange(focusChange); + + // convert other AudioFocusInfo to corresponding FocusRequester + ArrayList<FocusRequester> otherActiveFrs = new ArrayList<>(); + for (int index = 0; index < otherActiveAfis.size(); index++) { + FocusRequester otherFr = getFocusRequesterLocked( + otherActiveAfis.get(index).getClientId(), /* shouldRemove= */ false); + if (otherFr == null) { + continue; + } + otherActiveFrs.add(otherFr); + } + + int status = fr.dispatchFocusChangeWithFadeLocked(focusChange, otherActiveFrs); + if (status != AudioManager.AUDIOFOCUS_REQUEST_DELAYED + && focusChange == AudioManager.AUDIOFOCUS_LOSS) { + mFocusOwnersForFocusPolicy.remove(clientId); + } + + return status; + } + } + + @GuardedBy("mAudioFocusLock") + private FocusRequester getFocusRequesterLocked(String clientId, boolean shouldRemove) { + if (mFocusPolicy == null) { + if (DEBUG) { + Log.v(TAG, "> failed: no focus policy"); + } + return null; + } + + FocusRequester fr; + if (shouldRemove) { + fr = mFocusOwnersForFocusPolicy.remove(clientId); + } else { + fr = mFocusOwnersForFocusPolicy.get(clientId); + } + + if (fr == null && DEBUG) { + Log.v(TAG, "> failed: no such focus requester known"); } + return fr; } private void dumpExtFocusPolicyFocusOwners(PrintWriter pw) { diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index bc9b9b4b1c88..e69fbbd2a083 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -38,6 +38,7 @@ import android.media.AudioPlaybackConfiguration; import android.media.AudioPlaybackConfiguration.FormatInfo; import android.media.AudioPlaybackConfiguration.PlayerMuteEvent; import android.media.AudioSystem; +import android.media.FadeManagerConfiguration; import android.media.IPlaybackConfigDispatcher; import android.media.PlayerBase; import android.media.VolumeShaper; @@ -156,8 +157,7 @@ public final class PlaybackActivityMonitor private final int mMaxAlarmVolume; private int mPrivilegedAlarmActiveCount = 0; private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb; - private final FadeOutManager mFadeOutManager; - + private final FadeOutManager mFadeOutManager = new FadeOutManager(); PlaybackActivityMonitor(Context context, int maxAlarmVolume, Consumer<AudioDeviceAttributes> muteTimeoutCallback) { @@ -167,7 +167,6 @@ public final class PlaybackActivityMonitor AudioPlaybackConfiguration.sPlayerDeathMonitor = this; mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback; initEventHandler(); - mFadeOutManager = new FadeOutManager(new FadeConfigurations()); } //================================================================= @@ -971,6 +970,12 @@ public final class PlaybackActivityMonitor return mFadeOutManager.getFadeInDelayForOffendersMillis(aa); } + @Override + public boolean shouldEnforceFade() { + return mFadeOutManager.isFadeEnabled(); + } + + //================================================================= // Track playback activity listeners @@ -1010,6 +1015,27 @@ public final class PlaybackActivityMonitor } } + int setFadeManagerConfiguration(int focusType, FadeManagerConfiguration fadeMgrConfig) { + return mFadeOutManager.setFadeManagerConfiguration(fadeMgrConfig); + } + + int clearFadeManagerConfiguration(int focusType) { + return mFadeOutManager.clearFadeManagerConfiguration(); + } + + FadeManagerConfiguration getFadeManagerConfiguration(int focusType) { + return mFadeOutManager.getFadeManagerConfiguration(); + } + + int setTransientFadeManagerConfiguration(int focusType, + FadeManagerConfiguration fadeMgrConfig) { + return mFadeOutManager.setTransientFadeManagerConfiguration(fadeMgrConfig); + } + + int clearTransientFadeManagerConfiguration(int focusType) { + return mFadeOutManager.clearTransientFadeManagerConfiguration(); + } + /** * Inner class to track clients that want to be notified of playback updates */ @@ -1337,6 +1363,38 @@ public final class PlaybackActivityMonitor } } + static final class FadeEvent extends EventLogger.Event { + private final int mPlayerIId; + private final int mPlayerType; + private final int mClientUid; + private final int mClientPid; + private final AudioAttributes mPlayerAttr; + private final VolumeShaper.Configuration mVShaper; + private final VolumeShaper.Operation mVOperation; + + FadeEvent(AudioPlaybackConfiguration apc, VolumeShaper.Configuration vShaper, + VolumeShaper.Operation vOperation) { + mPlayerIId = apc.getPlayerInterfaceId(); + mClientUid = apc.getClientUid(); + mClientPid = apc.getClientPid(); + mPlayerAttr = apc.getAudioAttributes(); + mPlayerType = apc.getPlayerType(); + mVShaper = vShaper; + mVOperation = vOperation; + } + + @Override + public String eventToString() { + return "Fade Event:" + " player piid:" + mPlayerIId + + " uid/pid:" + mClientUid + "/" + mClientPid + + " player type:" + + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType) + + " attr:" + mPlayerAttr + + " volume shaper:" + mVShaper + + " volume operation:" + mVOperation; + } + } + private abstract static class VolumeShaperEvent extends EventLogger.Event { private final int mPlayerIId; private final boolean mSkipRamp; diff --git a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java index f1d42f3571a9..4a29eca5eef7 100644 --- a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java +++ b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java @@ -79,4 +79,11 @@ public interface PlayerFocusEnforcer { * @return delay in milliseconds */ long getFadeInDelayForOffendersMillis(@NonNull AudioAttributes aa); + + /** + * Check if the fade should be enforced + * + * @return {@code true} if fade should be enforced, {@code false} otherwise + */ + boolean shouldEnforceFade(); }
\ No newline at end of file diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 2314bb772494..3024dd27b5c4 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -16,6 +16,8 @@ package com.android.server.display; +import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessModeToString; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -72,12 +74,14 @@ public class AutomaticBrightnessController { @IntDef(prefix = { "AUTO_BRIGHTNESS_MODE_" }, value = { AUTO_BRIGHTNESS_MODE_DEFAULT, AUTO_BRIGHTNESS_MODE_IDLE, + AUTO_BRIGHTNESS_MODE_DOZE }) @Retention(RetentionPolicy.SOURCE) public @interface AutomaticBrightnessMode{} public static final int AUTO_BRIGHTNESS_MODE_DEFAULT = 0; public static final int AUTO_BRIGHTNESS_MODE_IDLE = 1; + public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2; // How long the current sensor reading is assumed to be valid beyond the current time. // This provides a bit of prediction, as well as ensures that the weight for the last sample is @@ -616,12 +620,13 @@ public class AutomaticBrightnessController { pw.println(" mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName); pw.println(" mForegroundAppCategory=" + mForegroundAppCategory); pw.println(" mPendingForegroundAppCategory=" + mPendingForegroundAppCategory); - pw.println(" Current mode=" + mCurrentBrightnessMapper.getMode()); + pw.println(" Current mode=" + + autoBrightnessModeToString(mCurrentBrightnessMapper.getMode())); pw.println(); for (int i = 0; i < mBrightnessMappingStrategyMap.size(); i++) { - pw.println(" Mapper for mode " + modeToString(mBrightnessMappingStrategyMap.keyAt(i)) - + "="); + pw.println(" Mapper for mode " + + autoBrightnessModeToString(mBrightnessMappingStrategyMap.keyAt(i)) + "="); mBrightnessMappingStrategyMap.valueAt(i).dump(pw, mBrightnessRangeController.getNormalBrightnessMax()); } @@ -1224,14 +1229,6 @@ public class AutomaticBrightnessController { } } - private String modeToString(@AutomaticBrightnessMode int mode) { - return switch (mode) { - case AUTO_BRIGHTNESS_MODE_DEFAULT -> "default"; - case AUTO_BRIGHTNESS_MODE_IDLE -> "idle"; - default -> Integer.toString(mode); - }; - } - private class ShortTermModel { // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the // user's adjustment) immediately, but wait for a drastic enough change in the ambient diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index acd253b38b3d..6a4b00f7551f 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -19,6 +19,7 @@ package com.android.server.display; import static android.text.TextUtils.formatSimple; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE; import android.annotation.Nullable; @@ -35,7 +36,6 @@ import android.util.Slog; import android.util.Spline; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.display.BrightnessUtils; import com.android.internal.util.Preconditions; import com.android.server.display.utils.Plog; @@ -97,28 +97,21 @@ public abstract class BrightnessMappingStrategy { float[] brightnessLevels = null; float[] luxLevels = null; switch (mode) { - case AUTO_BRIGHTNESS_MODE_DEFAULT: + case AUTO_BRIGHTNESS_MODE_DEFAULT -> { brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(); - luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(); - - brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels(); - if (brightnessLevels == null || brightnessLevels.length == 0) { - // Load the old configuration in the range [0, 255]. The values need to be - // normalized to the range [0, 1]. - int[] brightnessLevelsInt = resources.getIntArray( - com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); - brightnessLevels = new float[brightnessLevelsInt.length]; - for (int i = 0; i < brightnessLevels.length; i++) { - brightnessLevels[i] = normalizeAbsoluteBrightness(brightnessLevelsInt[i]); - } - } - break; - case AUTO_BRIGHTNESS_MODE_IDLE: + luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode); + brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode); + } + case AUTO_BRIGHTNESS_MODE_IDLE -> { brightnessLevelsNits = getFloatArray(resources.obtainTypedArray( com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle)); luxLevels = getLuxLevels(resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevelsIdle)); - break; + } + case AUTO_BRIGHTNESS_MODE_DOZE -> { + luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode); + brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode); + } } // Display independent, mode independent values @@ -426,11 +419,6 @@ public abstract class BrightnessMappingStrategy { } } - // Normalize entire brightness range to 0 - 1. - protected static float normalizeAbsoluteBrightness(int brightness) { - return BrightnessSynchronizer.brightnessIntToFloat(brightness); - } - private Pair<float[], float[]> insertControlPoint( float[] luxLevels, float[] brightnessLevels, float lux, float brightness) { final int idx = findInsertionPoint(luxLevels, lux); diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index d97127c91fbf..a6f42d7677b2 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -49,6 +49,7 @@ import com.android.server.display.config.BrightnessThresholds; import com.android.server.display.config.BrightnessThrottlingMap; import com.android.server.display.config.BrightnessThrottlingPoint; import com.android.server.display.config.Density; +import com.android.server.display.config.DisplayBrightnessMappingConfig; import com.android.server.display.config.DisplayBrightnessPoint; import com.android.server.display.config.DisplayConfiguration; import com.android.server.display.config.DisplayQuirks; @@ -57,7 +58,6 @@ import com.android.server.display.config.HdrBrightnessData; import com.android.server.display.config.HighBrightnessMode; import com.android.server.display.config.IntegerArray; import com.android.server.display.config.LuxThrottling; -import com.android.server.display.config.LuxToBrightnessMapping; import com.android.server.display.config.NitsMap; import com.android.server.display.config.NonNegativeFloatToFloatPoint; import com.android.server.display.config.Point; @@ -313,6 +313,21 @@ import javax.xml.datatype.DatatypeConfigurationException; * 1000 * </darkeningLightDebounceIdleMillis> * <luxToBrightnessMapping> + * <mode>default</mode> + * <map> + * <point> + * <first>0</first> + * <second>0.2</second> + * </point> + * <point> + * <first>80</first> + * <second>0.3</second> + * </point> + * </map> + * </luxToBrightnessMapping> + * <luxToBrightnessMapping> + * <mode>doze</mode> + * <setting>dim</setting> * <map> * <point> * <first>0</first> @@ -634,36 +649,8 @@ public class DisplayDeviceConfig { // for the corresponding values above private float[] mBrightness; - /** - * Array of desired screen brightness in nits corresponding to the lux values - * in the mBrightnessLevelsLux array. The display brightness is defined as the - * measured brightness of an all-white image. The brightness values must be non-negative and - * non-decreasing. This must be overridden in platform specific overlays - */ - private float[] mBrightnessLevelsNits; - - /** - * Array of desired screen brightness corresponding to the lux values - * in the mBrightnessLevelsLux array. The brightness values must be non-negative and - * non-decreasing. They must be between {@link PowerManager.BRIGHTNESS_MIN} and - * {@link PowerManager.BRIGHTNESS_MAX}. This must be overridden in platform specific overlays - */ - private float[] mBrightnessLevels; - - /** - * Array of light sensor lux values to define our levels for auto-brightness support. - * - * The first lux value is always 0. - * - * The control points must be strictly increasing. Each control point corresponds to an entry - * in the brightness values arrays. For example, if lux == luxLevels[1] (second element - * of the levels array) then the brightness will be determined by brightnessLevels[1] (second - * element of the brightness values array). - * - * Spline interpolation is used to determine the auto-brightness values for lux levels between - * these control points. - */ - private float[] mBrightnessLevelsLux; + @Nullable + private DisplayBrightnessMappingConfig mDisplayBrightnessMapping; private float mBacklightMinimum = Float.NaN; private float mBacklightMaximum = Float.NaN; @@ -1604,24 +1591,65 @@ public class DisplayDeviceConfig { } /** - * @return Auto brightness brightening ambient lux levels + * @param mode The auto-brightness mode + * @return The default auto-brightness brightening ambient lux levels for the specified mode + * and the normal brightness preset + */ + public float[] getAutoBrightnessBrighteningLevelsLux( + @AutomaticBrightnessController.AutomaticBrightnessMode int mode) { + if (mDisplayBrightnessMapping == null) { + return null; + } + return mDisplayBrightnessMapping.getLuxArray(mode); + } + + /** + * @param mode The auto-brightness mode + * @param preset The brightness preset. Presets are used on devices that allow users to choose + * from a set of predefined options in display auto-brightness settings. + * @return Auto brightness brightening ambient lux levels for the specified mode and preset */ - public float[] getAutoBrightnessBrighteningLevelsLux() { - return mBrightnessLevelsLux; + public float[] getAutoBrightnessBrighteningLevelsLux(String mode, String preset) { + if (mDisplayBrightnessMapping == null) { + return null; + } + return mDisplayBrightnessMapping.getLuxArray(mode, preset); } /** * @return Auto brightness brightening nits levels */ public float[] getAutoBrightnessBrighteningLevelsNits() { - return mBrightnessLevelsNits; + if (mDisplayBrightnessMapping == null) { + return null; + } + return mDisplayBrightnessMapping.getNitsArray(); } /** - * @return Auto brightness brightening levels + * @param mode The auto-brightness mode + * @return The default auto-brightness brightening levels for the specified mode and the normal + * brightness preset */ - public float[] getAutoBrightnessBrighteningLevels() { - return mBrightnessLevels; + public float[] getAutoBrightnessBrighteningLevels( + @AutomaticBrightnessController.AutomaticBrightnessMode int mode) { + if (mDisplayBrightnessMapping == null) { + return null; + } + return mDisplayBrightnessMapping.getBrightnessArray(mode); + } + + /** + * @param mode The auto-brightness mode + * @param preset The brightness preset. Presets are used on devices that allow users to choose + * from a set of predefined options in display auto-brightness settings. + * @return Auto brightness brightening backlight levels for the specified mode and preset + */ + public float[] getAutoBrightnessBrighteningLevels(String mode, String preset) { + if (mDisplayBrightnessMapping == null) { + return null; + } + return mDisplayBrightnessMapping.getBrightnessArray(mode, preset); } /** @@ -1875,9 +1903,7 @@ public class DisplayDeviceConfig { + mAutoBrightnessBrighteningLightDebounceIdle + ", mAutoBrightnessDarkeningLightDebounceIdle= " + mAutoBrightnessDarkeningLightDebounceIdle - + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux) - + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits) - + ", mBrightnessLevels= " + Arrays.toString(mBrightnessLevels) + + ", mDisplayBrightnessMapping= " + mDisplayBrightnessMapping + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable + "\n" @@ -2568,7 +2594,8 @@ public class DisplayDeviceConfig { // Idle must be called after interactive, since we fall back to it if needed. loadAutoBrightnessBrighteningLightDebounceIdle(autoBrightness); loadAutoBrightnessDarkeningLightDebounceIdle(autoBrightness); - loadAutoBrightnessDisplayBrightnessMapping(autoBrightness); + mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags, + autoBrightness, mBacklightToBrightnessSpline); loadEnableAutoBrightness(autoBrightness); } @@ -2633,38 +2660,6 @@ public class DisplayDeviceConfig { } } - /** - * Loads the auto-brightness display brightness mappings. Internally, this takes care of - * loading the value from the display config, and if not present, falls back to config.xml. - */ - private void loadAutoBrightnessDisplayBrightnessMapping(AutoBrightness autoBrightnessConfig) { - if (mFlags.areAutoBrightnessModesEnabled() && autoBrightnessConfig != null - && autoBrightnessConfig.getLuxToBrightnessMapping() != null) { - LuxToBrightnessMapping mapping = autoBrightnessConfig.getLuxToBrightnessMapping(); - final int size = mapping.getMap().getPoint().size(); - mBrightnessLevels = new float[size]; - mBrightnessLevelsLux = new float[size]; - for (int i = 0; i < size; i++) { - float backlight = mapping.getMap().getPoint().get(i).getSecond().floatValue(); - mBrightnessLevels[i] = mBacklightToBrightnessSpline.interpolate(backlight); - mBrightnessLevelsLux[i] = mapping.getMap().getPoint().get(i).getFirst() - .floatValue(); - } - if (size > 0 && mBrightnessLevelsLux[0] != 0) { - throw new IllegalArgumentException( - "The first lux value in the display brightness mapping must be 0"); - } - } else { - mBrightnessLevelsNits = getFloatArray(mContext.getResources() - .obtainTypedArray(com.android.internal.R.array - .config_autoBrightnessDisplayValuesNits), PowerManager - .BRIGHTNESS_OFF_FLOAT); - mBrightnessLevelsLux = getLuxLevels(mContext.getResources() - .getIntArray(com.android.internal.R.array - .config_autoBrightnessLevels)); - } - } - private void loadAutoBrightnessAvailableFromConfigXml() { mAutoBrightnessAvailable = mContext.getResources().getBoolean( R.bool.config_automatic_brightness_available); @@ -2977,7 +2972,8 @@ public class DisplayDeviceConfig { } private void loadAutoBrightnessConfigsFromConfigXml() { - loadAutoBrightnessDisplayBrightnessMapping(null /*AutoBrightnessConfig*/); + mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags, + /* autoBrightnessConfig= */ null, mBacklightToBrightnessSpline); } private void loadBrightnessChangeThresholdsFromXml() { @@ -3347,7 +3343,12 @@ public class DisplayDeviceConfig { return vals; } - private static float[] getLuxLevels(int[] lux) { + /** + * @param lux The lux array + * @return The lux array with 0 appended at the beginning - the first lux value should always + * be 0 + */ + public static float[] getLuxLevels(int[] lux) { // The first control point is implicit and always at 0 lux. float[] levels = new float[lux.length + 1]; for (int i = 0; i < lux.length; i++) { diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 6d09cc9d37ba..c088a6dfa95f 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -17,6 +17,7 @@ package com.android.server.display; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE; import android.animation.Animator; @@ -1006,6 +1007,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } } + BrightnessMappingStrategy dozeModeBrightnessMapper = + BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig, + AUTO_BRIGHTNESS_MODE_DOZE, mDisplayWhiteBalanceController); + if (mFlags.areAutoBrightnessModesEnabled() && dozeModeBrightnessMapper != null) { + brightnessMappers.put(AUTO_BRIGHTNESS_MODE_DOZE, dozeModeBrightnessMapper); + } + float userLux = BrightnessMappingStrategy.INVALID_LUX; float userNits = BrightnessMappingStrategy.INVALID_NITS; if (mAutomaticBrightnessController != null) { @@ -1349,6 +1357,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition()); state = mPowerState.getScreenState(); + // Switch to doze auto-brightness mode if needed + if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null + && !mAutomaticBrightnessController.isInIdleMode()) { + setAutomaticScreenBrightnessMode(Display.isDozeState(state) + ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT); + } + final boolean userSetBrightnessChanged = mDisplayBrightnessController .updateUserSetScreenBrightness(); diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java new file mode 100644 index 000000000000..8f123291d89c --- /dev/null +++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java @@ -0,0 +1,259 @@ +/* + * 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.config; + +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE; + +import android.content.Context; +import android.os.PowerManager; +import android.util.Spline; + +import com.android.internal.display.BrightnessSynchronizer; +import com.android.server.display.AutomaticBrightnessController; +import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.feature.DisplayManagerFlags; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Provides a mapping between lux and brightness values in order to support auto-brightness. + */ +public class DisplayBrightnessMappingConfig { + + private static final String DEFAULT_BRIGHTNESS_PRESET_NAME = "normal"; + private static final String DEFAULT_BRIGHTNESS_MAPPING_KEY = + AutoBrightnessModeName._default.getRawName() + "_" + DEFAULT_BRIGHTNESS_PRESET_NAME; + + /** + * Array of desired screen brightness in nits corresponding to the lux values + * in the mBrightnessLevelsLuxMap.get(DEFAULT_ID) array. The display brightness is defined as + * the measured brightness of an all-white image. The brightness values must be non-negative and + * non-decreasing. This must be overridden in platform specific overlays + */ + private float[] mBrightnessLevelsNits; + + /** + * Map of arrays of desired screen brightness corresponding to the lux values + * in mBrightnessLevelsLuxMap, indexed by the auto-brightness mode and the brightness preset. + * The brightness values must be non-negative and non-decreasing. They must be between + * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}. + * + * The keys are a concatenation of the auto-brightness mode and the brightness preset separated + * by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal, doze_dim, + * doze_bright. + * + * The presets are used on devices that allow users to choose from a set of predefined options + * in display auto-brightness settings. + */ + private final Map<String, float[]> mBrightnessLevelsMap = new HashMap<>(); + + /** + * Map of arrays of light sensor lux values to define our levels for auto-brightness support, + * indexed by the auto-brightness mode and the brightness preset. + * + * The first lux value in every array is always 0. + * + * The control points must be strictly increasing. Each control point corresponds to an entry + * in the brightness values arrays. For example, if lux == luxLevels[1] (second element + * of the levels array) then the brightness will be determined by brightnessLevels[1] (second + * element of the brightness values array). + * + * Spline interpolation is used to determine the auto-brightness values for lux levels between + * these control points. + * + * The keys are a concatenation of the auto-brightness mode and the brightness preset separated + * by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal, doze_dim, + * doze_bright. + * + * The presets are used on devices that allow users to choose from a set of predefined options + * in display auto-brightness settings. + */ + private final Map<String, float[]> mBrightnessLevelsLuxMap = new HashMap<>(); + + /** + * Loads the auto-brightness display brightness mappings. Internally, this takes care of + * loading the value from the display config, and if not present, falls back to config.xml. + */ + public DisplayBrightnessMappingConfig(Context context, DisplayManagerFlags flags, + AutoBrightness autoBrightnessConfig, Spline backlightToBrightnessSpline) { + if (flags.areAutoBrightnessModesEnabled() && autoBrightnessConfig != null + && autoBrightnessConfig.getLuxToBrightnessMapping() != null + && autoBrightnessConfig.getLuxToBrightnessMapping().size() > 0) { + for (LuxToBrightnessMapping mapping + : autoBrightnessConfig.getLuxToBrightnessMapping()) { + final int size = mapping.getMap().getPoint().size(); + float[] brightnessLevels = new float[size]; + float[] brightnessLevelsLux = new float[size]; + for (int i = 0; i < size; i++) { + float backlight = mapping.getMap().getPoint().get(i).getSecond().floatValue(); + brightnessLevels[i] = backlightToBrightnessSpline.interpolate(backlight); + brightnessLevelsLux[i] = mapping.getMap().getPoint().get(i).getFirst() + .floatValue(); + } + if (size == 0) { + throw new IllegalArgumentException( + "A display brightness mapping should not be empty"); + } + if (brightnessLevelsLux[0] != 0) { + throw new IllegalArgumentException( + "The first lux value in the display brightness mapping must be 0"); + } + + String key = (mapping.getMode() == null ? "default" : mapping.getMode()) + "_" + + (mapping.getSetting() == null ? "normal" : mapping.getSetting()); + if (mBrightnessLevelsMap.containsKey(key) + || mBrightnessLevelsLuxMap.containsKey(key)) { + throw new IllegalArgumentException( + "A display brightness mapping with key " + key + " already exists"); + } + mBrightnessLevelsMap.put(key, brightnessLevels); + mBrightnessLevelsLuxMap.put(key, brightnessLevelsLux); + } + } + + if (!mBrightnessLevelsMap.containsKey(DEFAULT_BRIGHTNESS_MAPPING_KEY) + || !mBrightnessLevelsLuxMap.containsKey(DEFAULT_BRIGHTNESS_MAPPING_KEY)) { + mBrightnessLevelsNits = DisplayDeviceConfig.getFloatArray(context.getResources() + .obtainTypedArray(com.android.internal.R.array + .config_autoBrightnessDisplayValuesNits), PowerManager + .BRIGHTNESS_OFF_FLOAT); + + float[] brightnessLevelsLux = DisplayDeviceConfig.getLuxLevels(context.getResources() + .getIntArray(com.android.internal.R.array + .config_autoBrightnessLevels)); + mBrightnessLevelsLuxMap.put(DEFAULT_BRIGHTNESS_MAPPING_KEY, brightnessLevelsLux); + + // Load the old configuration in the range [0, 255]. The values need to be normalized + // to the range [0, 1]. + int[] brightnessLevels = context.getResources().getIntArray( + com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); + mBrightnessLevelsMap.put(DEFAULT_BRIGHTNESS_MAPPING_KEY, + brightnessArrayIntToFloat(brightnessLevels, backlightToBrightnessSpline)); + } + } + + /** + * @param mode The auto-brightness mode + * @return The default auto-brightness brightening ambient lux levels for the specified mode + * and the normal brightness preset + */ + public float[] getLuxArray(@AutomaticBrightnessController.AutomaticBrightnessMode int mode) { + return mBrightnessLevelsLuxMap.get( + autoBrightnessModeToString(mode) + "_" + DEFAULT_BRIGHTNESS_PRESET_NAME); + } + + /** + * @param mode The auto-brightness mode + * @param preset The brightness preset. Presets are used on devices that allow users to choose + * from a set of predefined options in display auto-brightness settings. + * @return Auto brightness brightening ambient lux levels for the specified mode and preset + */ + public float[] getLuxArray(String mode, String preset) { + return mBrightnessLevelsLuxMap.get(mode + "_" + preset); + } + + /** + * @return Auto brightness brightening nits levels + */ + public float[] getNitsArray() { + return mBrightnessLevelsNits; + } + + /** + * @param mode The auto-brightness mode + * @return The default auto-brightness brightening levels for the specified mode and the normal + * brightness preset + */ + public float[] getBrightnessArray( + @AutomaticBrightnessController.AutomaticBrightnessMode int mode) { + return mBrightnessLevelsMap.get( + autoBrightnessModeToString(mode) + "_" + DEFAULT_BRIGHTNESS_PRESET_NAME); + } + + /** + * @param mode The auto-brightness mode + * @param preset The brightness preset. Presets are used on devices that allow users to choose + * from a set of predefined options in display auto-brightness settings. + * @return Auto brightness brightening ambient lux levels for the specified mode and preset + */ + public float[] getBrightnessArray(String mode, String preset) { + return mBrightnessLevelsMap.get(mode + "_" + preset); + } + + @Override + public String toString() { + StringBuilder brightnessLevelsLuxMapString = new StringBuilder("{"); + for (Map.Entry<String, float[]> entry : mBrightnessLevelsLuxMap.entrySet()) { + brightnessLevelsLuxMapString.append(entry.getKey()).append("=").append( + Arrays.toString(entry.getValue())).append(", "); + } + if (brightnessLevelsLuxMapString.length() > 2) { + brightnessLevelsLuxMapString.delete(brightnessLevelsLuxMapString.length() - 2, + brightnessLevelsLuxMapString.length()); + } + brightnessLevelsLuxMapString.append("}"); + + StringBuilder brightnessLevelsMapString = new StringBuilder("{"); + for (Map.Entry<String, float[]> entry : mBrightnessLevelsMap.entrySet()) { + brightnessLevelsMapString.append(entry.getKey()).append("=").append( + Arrays.toString(entry.getValue())).append(", "); + } + if (brightnessLevelsMapString.length() > 2) { + brightnessLevelsMapString.delete(brightnessLevelsMapString.length() - 2, + brightnessLevelsMapString.length()); + } + brightnessLevelsMapString.append("}"); + + return "mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits) + + ", mBrightnessLevelsLuxMap= " + brightnessLevelsLuxMapString + + ", mBrightnessLevelsMap= " + brightnessLevelsMapString; + } + + /** + * @param mode The auto-brightness mode + * @return The string representing the mode + */ + public static String autoBrightnessModeToString( + @AutomaticBrightnessController.AutomaticBrightnessMode int mode) { + switch (mode) { + case AUTO_BRIGHTNESS_MODE_DEFAULT -> { + return AutoBrightnessModeName._default.getRawName(); + } + case AUTO_BRIGHTNESS_MODE_IDLE -> { + return AutoBrightnessModeName.idle.getRawName(); + } + case AUTO_BRIGHTNESS_MODE_DOZE -> { + return AutoBrightnessModeName.doze.getRawName(); + } + default -> throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode); + } + } + + private float[] brightnessArrayIntToFloat(int[] brightnessInt, + Spline backlightToBrightnessSpline) { + float[] brightnessFloat = new float[brightnessInt.length]; + for (int i = 0; i < brightnessInt.length; i++) { + brightnessFloat[i] = backlightToBrightnessSpline.interpolate( + BrightnessSynchronizer.brightnessIntToFloat(brightnessInt[i])); + } + return brightnessFloat; + } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 64abb81d0e7a..81204ef5d7ed 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -1314,7 +1314,6 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice { */ protected void disableDevice( boolean initiatedByCec, final PendingActionClearedCallback originalCallback) { - removeAction(AbsoluteVolumeAudioStatusAction.class); removeAction(SetAudioVolumeLevelDiscoveryAction.class); removeAction(ActiveSourceAction.class); removeAction(ResendCecCommandAction.class); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java index 29303aab6fa9..6157402279a9 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java @@ -308,7 +308,6 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { removeAction(OneTouchPlayAction.class); removeAction(DevicePowerStatusAction.class); - removeAction(AbsoluteVolumeAudioStatusAction.class); super.disableDevice(initiatedByCec, callback); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 5831b29437dc..1cd267dee2fe 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -1336,7 +1336,6 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { removeAction(OneTouchRecordAction.class); removeAction(TimerRecordingAction.class); removeAction(NewDeviceAction.class); - removeAction(AbsoluteVolumeAudioStatusAction.class); // Remove pending actions. removeAction(RequestActiveSourceAction.class); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 92537064f766..eaf754dc7520 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -3793,6 +3793,11 @@ public class HdmiControlService extends SystemService { } } }); + + // Make sure we switch away from absolute volume behavior (AVB) when entering standby. + // We do this because AVB should not be used unless AbsoluteVolumeAudioStatusAction exists, + // and the action cannot exist in standby because there are no local devices. + checkAndUpdateAbsoluteVolumeBehavior(); } boolean canGoToStandby() { @@ -4446,10 +4451,11 @@ public class HdmiControlService extends SystemService { * This allows the volume level of the System Audio device to be tracked and set by Android. * * Absolute volume behavior requires the following conditions: - * 1. If the System Audio Device is an Audio System: System Audio Mode is active - * 2. All AVB-capable audio output devices are already using full/absolute volume behavior - * 3. CEC volume is enabled - * 4. The System Audio device supports the <Set Audio Volume Level> message + * 1. The device is not in standby or transient to standby + * 2. If the System Audio Device is an Audio System: System Audio Mode is active + * 3. All AVB-capable audio output devices are already using full/absolute volume behavior + * 4. CEC volume is enabled + * 5. The System Audio device supports the <Set Audio Volume Level> message * * This method enables adjust-only absolute volume behavior on TV panels when conditions * 1, 2, and 3 are met, but condition 4 is not. This allows TVs to track the volume level of @@ -4465,10 +4471,16 @@ public class HdmiControlService extends SystemService { return; } + // Condition 1: The device is not in standby or transient to standby + if (mPowerStatusController != null && isPowerStandbyOrTransient()) { + switchToFullVolumeBehavior(); + return; + } + HdmiCecLocalDevice localCecDevice; if (isTvDevice() && tv() != null) { localCecDevice = tv(); - // Condition 1: TVs need System Audio Mode to be active + // Condition 2: TVs need System Audio Mode to be active // (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the // TV is the System Audio Device instead.) if (!isSystemAudioActivated()) { @@ -4485,7 +4497,7 @@ public class HdmiControlService extends SystemService { HdmiDeviceInfo systemAudioDeviceInfo = getDeviceInfo( localCecDevice.findAudioReceiverAddress()); - // Condition 2: All AVB-capable audio outputs already use full/absolute volume behavior + // Condition 3: All AVB-capable audio outputs already use full/absolute volume behavior // We only need to check the first AVB-capable audio output because only TV panels // have more than one of them, and they always have the same volume behavior. @AudioManager.DeviceVolumeBehavior int currentVolumeBehavior = @@ -4493,7 +4505,7 @@ public class HdmiControlService extends SystemService { boolean alreadyUsingFullOrAbsoluteVolume = FULL_AND_ABSOLUTE_VOLUME_BEHAVIORS.contains(currentVolumeBehavior); - // Condition 3: CEC volume is enabled + // Condition 4: CEC volume is enabled boolean cecVolumeEnabled = getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED; @@ -4509,7 +4521,7 @@ public class HdmiControlService extends SystemService { return; } - // Condition 4: The System Audio device supports <Set Audio Volume Level> + // Condition 5: The System Audio device supports <Set Audio Volume Level> switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) { case DeviceFeatures.FEATURE_SUPPORTED: if (currentVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) { @@ -4556,6 +4568,8 @@ public class HdmiControlService extends SystemService { * are currently used. Removes the action for handling volume updates for these behaviors. */ private void switchToFullVolumeBehavior() { + Slog.d(TAG, "Switching to full volume behavior"); + if (playback() != null) { playback().removeAvbAudioStatusAction(); } else if (tv() != null) { @@ -4597,12 +4611,14 @@ public class HdmiControlService extends SystemService { // Otherwise, enable adjust-only AVB on TVs only. if (systemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport() == DeviceFeatures.FEATURE_SUPPORTED) { + Slog.d(TAG, "Enabling absolute volume behavior"); for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) { getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior( device, volumeInfo, mServiceThreadExecutor, mAbsoluteVolumeChangedListener, true); } } else if (tv() != null) { + Slog.d(TAG, "Enabling adjust-only absolute volume behavior"); for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) { getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeAdjustOnlyBehavior( device, volumeInfo, mServiceThreadExecutor, diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index f2dcba45e312..5514ec701c15 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -59,6 +59,7 @@ import android.util.apk.SourceStampVerifier; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.PackageParser2; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FrameworkStatsLog; @@ -67,7 +68,7 @@ import com.android.server.integrity.engine.RuleEvaluationEngine; import com.android.server.integrity.model.IntegrityCheckResult; import com.android.server.integrity.model.RuleMetadata; import com.android.server.pm.PackageManagerServiceUtils; -import com.android.server.pm.parsing.PackageParser2; +import com.android.server.pm.parsing.PackageParserUtils; import java.io.ByteArrayInputStream; import java.io.File; @@ -141,7 +142,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return new AppIntegrityManagerServiceImpl( context, LocalServices.getService(PackageManagerInternal.class), - PackageParser2::forParsingFileWithDefaults, + PackageParserUtils::forParsingFileWithDefaults, RuleEvaluationEngine.getRuleEvaluationEngine(), IntegrityFileManager.getInstance(), handlerThread.getThreadHandler()); diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java index 78c8cdee7845..403b421639cb 100644 --- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java +++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java @@ -19,6 +19,7 @@ package com.android.server.location.gnss; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import android.content.Context; +import android.location.flags.Flags; import android.net.ConnectivityManager; import android.net.LinkAddress; import android.net.LinkProperties; @@ -48,6 +49,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; /** * Handles network connection requests and network state change updates for AGPS data download. @@ -91,6 +93,10 @@ class GnssNetworkConnectivityHandler { // network with SUPL connectivity or report an error. private static final int SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS = 20 * 1000; + // If the chipset does not request to release a SUPL connection before the specified timeout in + // milliseconds, the connection will be automatically released. + private static final long SUPL_CONNECTION_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1); + private static final int HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS = 5; // Keeps track of networks and their state as notified by the network request callbacks. @@ -121,6 +127,8 @@ class GnssNetworkConnectivityHandler { private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000; private final PowerManager.WakeLock mWakeLock; + private final Object mSuplConnectionReleaseOnTimeoutToken = new Object(); + /** * Network attributes needed when updating HAL about network connectivity status changes. */ @@ -609,6 +617,13 @@ class GnssNetworkConnectivityHandler { mSuplConnectivityCallback, mHandler, SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS); + if (Flags.releaseSuplConnectionOnTimeout()) { + // Schedule to release the SUPL connection after timeout + mHandler.removeCallbacksAndMessages(mSuplConnectionReleaseOnTimeoutToken); + mHandler.postDelayed(() -> handleReleaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN), + mSuplConnectionReleaseOnTimeoutToken, + SUPL_CONNECTION_TIMEOUT_MILLIS); + } } catch (RuntimeException e) { Log.e(TAG, "Failed to request network.", e); mSuplConnectivityCallback = null; @@ -639,6 +654,10 @@ class GnssNetworkConnectivityHandler { Log.d(TAG, message); } + if (Flags.releaseSuplConnectionOnTimeout()) { + // Remove pending task to avoid releasing an incorrect connection + mHandler.removeCallbacksAndMessages(mSuplConnectionReleaseOnTimeoutToken); + } if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_CLOSED) { return; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a919db947593..135a467cc6b0 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -7018,12 +7018,14 @@ public class NotificationManagerService extends SystemService { return false; } - final boolean hasBitmap = n.extras.containsKey(Notification.EXTRA_PICTURE); + final boolean hasBitmap = n.extras.containsKey(Notification.EXTRA_PICTURE) + && n.extras.getParcelable(Notification.EXTRA_PICTURE) != null; if (hasBitmap) { return true; } - final boolean hasIcon = n.extras.containsKey(Notification.EXTRA_PICTURE_ICON); + final boolean hasIcon = n.extras.containsKey(Notification.EXTRA_PICTURE_ICON) + && n.extras.getParcelable(Notification.EXTRA_PICTURE_ICON) != null; if (hasIcon) { return true; } @@ -7039,9 +7041,10 @@ public class NotificationManagerService extends SystemService { if (!isBigPictureWithBitmapOrIcon(r.getNotification())) { return; } - // Remove Notification object's reference to picture bitmap or URI - r.getNotification().extras.remove(Notification.EXTRA_PICTURE); - r.getNotification().extras.remove(Notification.EXTRA_PICTURE_ICON); + // Remove Notification object's reference to picture bitmap or URI. Leave the extras set to + // null to avoid crashing apps that came to expect them to be present but null. + r.getNotification().extras.putParcelable(Notification.EXTRA_PICTURE, null); + r.getNotification().extras.putParcelable(Notification.EXTRA_PICTURE_ICON, null); // Make Notification silent r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java index 87158cd6fe29..df570a02eba5 100644 --- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java +++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java @@ -29,6 +29,7 @@ import android.content.pm.PackageManager; import android.os.Process; import android.service.notification.DNDPolicyProto; import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ConfigChangeOrigin; import android.service.notification.ZenModeDiff; import android.service.notification.ZenPolicy; import android.util.ArrayMap; @@ -58,7 +59,7 @@ class ZenModeEventLogger { // mode change. ZenModeEventLogger.ZenStateChanges mChangeState = new ZenModeEventLogger.ZenStateChanges(); - private PackageManager mPm; + private final PackageManager mPm; ZenModeEventLogger(PackageManager pm) { mPm = pm; @@ -97,11 +98,11 @@ class ZenModeEventLogger { * @param newInfo ZenModeInfo after this change takes effect * @param callingUid the calling UID associated with the change; may be used to attribute the * change to a particular package or determine if this is a user action - * @param fromSystemOrSystemUi whether the calling UID is either system UID or system UI + * @param origin The origin of the Zen change. */ public final void maybeLogZenChange(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid, - boolean fromSystemOrSystemUi) { - mChangeState.init(prevInfo, newInfo, callingUid, fromSystemOrSystemUi); + @ConfigChangeOrigin int origin) { + mChangeState.init(prevInfo, newInfo, callingUid, origin); if (mChangeState.shouldLogChanges()) { maybeReassignCallingUid(); logChanges(); @@ -124,7 +125,7 @@ class ZenModeEventLogger { // We don't consider the manual rule in the old config because if a manual rule is turning // off with a call from system, that could easily be a user action to explicitly turn it off if (mChangeState.getChangedRuleType() == RULE_TYPE_MANUAL) { - if (!mChangeState.mFromSystemOrSystemUi + if (!mChangeState.isFromSystemOrSystemUi() || mChangeState.getNewManualRuleEnabler() == null) { return; } @@ -136,7 +137,7 @@ class ZenModeEventLogger { // - we've determined it's not a user action // - our current best guess is that the calling uid is system/sysui if (mChangeState.getChangedRuleType() == RULE_TYPE_AUTOMATIC) { - if (mChangeState.getIsUserAction() || !mChangeState.mFromSystemOrSystemUi) { + if (mChangeState.getIsUserAction() || !mChangeState.isFromSystemOrSystemUi()) { return; } @@ -221,10 +222,10 @@ class ZenModeEventLogger { ZenModeConfig mPrevConfig, mNewConfig; NotificationManager.Policy mPrevPolicy, mNewPolicy; int mCallingUid = Process.INVALID_UID; - boolean mFromSystemOrSystemUi = false; + @ConfigChangeOrigin int mOrigin = ZenModeConfig.UPDATE_ORIGIN_UNKNOWN; private void init(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid, - boolean fromSystemOrSystemUi) { + @ConfigChangeOrigin int origin) { // previous & new may be the same -- that would indicate that zen mode hasn't changed. mPrevZenMode = prevInfo.mZenMode; mNewZenMode = newInfo.mZenMode; @@ -233,7 +234,7 @@ class ZenModeEventLogger { mPrevPolicy = prevInfo.mPolicy; mNewPolicy = newInfo.mPolicy; mCallingUid = callingUid; - mFromSystemOrSystemUi = fromSystemOrSystemUi; + mOrigin = origin; } /** @@ -389,12 +390,16 @@ class ZenModeEventLogger { /** * Return our best guess as to whether the changes observed are due to a user action. - * Note that this won't be 100% accurate as we can't necessarily distinguish between a - * system uid call indicating "user interacted with Settings" vs "a system app changed - * something automatically". + * Note that this (before {@code MODES_API}) won't be 100% accurate as we can't necessarily + * distinguish between a system uid call indicating "user interacted with Settings" vs "a + * system app changed something automatically". */ boolean getIsUserAction() { - // Approach: + if (Flags.modesApi()) { + return mOrigin == ZenModeConfig.UPDATE_ORIGIN_USER; + } + + // Approach for pre-MODES_API: // - if manual rule turned on or off, the calling UID is system, and the new manual // rule does not have an enabler set, guess that this is likely to be a user action. // This may represent a system app turning on DND automatically, but we guess "user" @@ -419,13 +424,13 @@ class ZenModeEventLogger { switch (getChangedRuleType()) { case RULE_TYPE_MANUAL: // TODO(b/278888961): Distinguish the automatically-turned-off state - return mFromSystemOrSystemUi && (getNewManualRuleEnabler() == null); + return isFromSystemOrSystemUi() && (getNewManualRuleEnabler() == null); case RULE_TYPE_AUTOMATIC: for (ZenModeDiff.RuleDiff d : getChangedAutomaticRules().values()) { if (d.wasAdded() || d.wasRemoved()) { // If the change comes from system, a rule being added/removed indicates // a likely user action. From an app, it's harder to know for sure. - return mFromSystemOrSystemUi; + return isFromSystemOrSystemUi(); } ZenModeDiff.FieldDiff enabled = d.getDiffForField( ZenModeDiff.RuleDiff.FIELD_ENABLED); @@ -455,6 +460,13 @@ class ZenModeEventLogger { return false; } + boolean isFromSystemOrSystemUi() { + return mOrigin == ZenModeConfig.UPDATE_ORIGIN_INIT + || mOrigin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER + || mOrigin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + || mOrigin == ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP; + } + /** * Get the package UID associated with this change, which is just the calling UID for the * relevant method changes. This may get reset by ZenModeEventLogger, which has access to @@ -612,7 +624,7 @@ class ZenModeEventLogger { copy.mPrevPolicy = mPrevPolicy.copy(); copy.mNewPolicy = mNewPolicy.copy(); copy.mCallingUid = mCallingUid; - copy.mFromSystemOrSystemUi = mFromSystemOrSystemUi; + copy.mOrigin = mOrigin; return copy; } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index d1de9b086c5d..0a46901a93d1 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -1371,12 +1371,8 @@ public class ZenModeHelper { if (logZenModeEvents) { ZenModeEventLogger.ZenModeInfo newInfo = new ZenModeEventLogger.ZenModeInfo( mZenMode, mConfig, mConsolidatedPolicy); - boolean fromSystemOrSystemUi = origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI - || origin == UPDATE_ORIGIN_INIT - || origin == UPDATE_ORIGIN_INIT_USER - || origin == UPDATE_ORIGIN_RESTORE_BACKUP; mZenModeEventLogger.maybeLogZenChange(prevInfo, newInfo, callingUid, - fromSystemOrSystemUi); + origin); } } diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 55d8a0fd1fa1..8e79922a996a 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -41,3 +41,12 @@ flag { description: "This flag controls the fix for notifications autogroup summary icon updates" bug: "227693160" } + +flag { + name: "sensitive_notification_app_protection" + namespace: "systemui" + description: "This flag controls the sensitive notification app protections while screen sharing" + bug: "312784351" + # Referenced in WM where WM starts before DeviceConfig + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java index 3b9f9c804e27..41d01762505d 100644 --- a/services/core/java/com/android/server/pm/InitAppsHelper.java +++ b/services/core/java/com/android/server/pm/InitAppsHelper.java @@ -46,11 +46,11 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.om.OverlayConfig; +import com.android.internal.pm.parsing.PackageParser2; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.EventLogTags; import com.android.server.pm.parsing.PackageCacher; -import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.utils.WatchedArrayMap; diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 65bfb2f258eb..8b38f947b952 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -146,6 +146,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; +import android.util.ExceptionUtils; import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -154,6 +155,7 @@ import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.F2fsUtils; +import com.android.internal.pm.parsing.PackageParser2; import com.android.internal.pm.parsing.PackageParserException; import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils; import com.android.internal.pm.parsing.pkg.ParsedPackage; @@ -178,7 +180,6 @@ import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DexoptOptions; import com.android.server.pm.parsing.PackageCacher; -import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.permission.Permission; import com.android.server.pm.permission.PermissionManagerServiceInternal; @@ -1167,8 +1168,9 @@ final class InstallPackageHelper { parseFlags); archivedPackage = request.getPackageLite().getArchivedPackage(); } - } catch (PackageManagerException | PackageParserException e) { - throw new PrepareFailure("Failed parse during installPackageLI", e); + } catch (PackageParserException e) { + throw new PrepareFailure(e.error, + ExceptionUtils.getCompleteMessage("Failed parse during installPackageLI", e)); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -3680,6 +3682,8 @@ final class InstallPackageHelper { final ParsedPackage parsedPackage; try (PackageParser2 pp = mPm.mInjector.getScanningPackageParser()) { parsedPackage = pp.parsePackage(scanFile, parseFlags, false); + } catch (PackageParserException e) { + throw new PackageManagerException(e.error, e.getMessage(), e); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java index 187cadaf4d46..e970d2c79b05 100644 --- a/services/core/java/com/android/server/pm/InstallingSession.java +++ b/services/core/java/com/android/server/pm/InstallingSession.java @@ -51,8 +51,8 @@ import android.util.Slog; import com.android.internal.content.F2fsUtils; import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.NativeLibraryHelper; +import com.android.internal.pm.parsing.PackageParser2; import com.android.internal.util.Preconditions; -import com.android.server.pm.parsing.PackageParser2; import libcore.io.IoUtils; diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 2864a8b42445..dcfc855da855 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -132,6 +132,11 @@ public class PackageArchiver { private static final String EXTRA_INSTALLER_TITLE = "com.android.content.pm.extra.UNARCHIVE_INSTALLER_TITLE"; + private static final PorterDuffColorFilter OPACITY_LAYER_FILTER = + new PorterDuffColorFilter( + Color.argb(0.5f /* alpha */, 0f /* red */, 0f /* green */, 0f /* blue */), + PorterDuff.Mode.SRC_ATOP); + private final Context mContext; private final PackageManagerService mPm; @@ -746,11 +751,7 @@ public class PackageArchiver { return bitmap; } BitmapDrawable appIconDrawable = new BitmapDrawable(mContext.getResources(), bitmap); - PorterDuffColorFilter colorFilter = - new PorterDuffColorFilter( - Color.argb(0.32f /* alpha */, 0f /* red */, 0f /* green */, 0f /* blue */), - PorterDuff.Mode.SRC_ATOP); - appIconDrawable.setColorFilter(colorFilter); + appIconDrawable.setColorFilter(OPACITY_LAYER_FILTER); appIconDrawable.setBounds( 0 /* left */, 0 /* top */, diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 2942bbb86e62..cbd65a4eb936 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -111,6 +111,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.InstallLocationUtils; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.pm.parsing.PackageParser2; import com.android.internal.util.ImageUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.modules.utils.TypedXmlPullParser; @@ -120,7 +121,6 @@ import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.SystemServiceManager; -import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.utils.RequestThrottle; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 1d5b8c3ed22a..81d5d8195960 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -181,6 +181,7 @@ import com.android.internal.app.ResolverActivity; import com.android.internal.content.F2fsUtils; import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.om.OverlayConfig; +import com.android.internal.pm.parsing.PackageParser2; import com.android.internal.pm.parsing.pkg.AndroidPackageInternal; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedInstrumentation; @@ -221,8 +222,8 @@ import com.android.server.pm.dex.ArtUtils; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DynamicCodeLogger; import com.android.server.pm.local.PackageManagerLocalImpl; +import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageInfoUtils; -import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.permission.LegacyPermissionManagerInternal; import com.android.server.pm.permission.LegacyPermissionManagerService; @@ -1698,7 +1699,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService () -> LocalServices.getService(UserManagerInternal.class)), (i, pm) -> new DisplayMetrics(), (i, pm) -> new PackageParser2(pm.mSeparateProcesses, i.getDisplayMetrics(), - pm.mCacheDir, + new PackageCacher(pm.mCacheDir), pm.mPackageParserCallback) /* scanningCachingPackageParserProducer */, (i, pm) -> new PackageParser2(pm.mSeparateProcesses, i.getDisplayMetrics(), null, pm.mPackageParserCallback) /* scanningPackageParserProducer */, diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java index ebf1c04bee12..049737d42f51 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java @@ -27,12 +27,12 @@ import android.os.incremental.IncrementalManager; import android.util.DisplayMetrics; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.PackageParser2; import com.android.server.SystemConfig; import com.android.server.compat.PlatformCompat; import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DynamicCodeLogger; -import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.permission.LegacyPermissionManagerInternal; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.resolution.ComponentResolver; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java index 655b9c93d9dd..86d78dce96c3 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java @@ -31,10 +31,10 @@ import android.util.DisplayMetrics; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.om.OverlayConfig; +import com.android.internal.pm.parsing.PackageParser2; import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DynamicCodeLogger; -import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.permission.LegacyPermissionManagerInternal; import com.android.server.pm.pkg.AndroidPackage; diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java index 15d2fdc35f2b..1fe49c7d5834 100644 --- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java +++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java @@ -41,10 +41,11 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.InstallLocationUtils; +import com.android.internal.pm.parsing.PackageParser2; +import com.android.internal.pm.parsing.PackageParserException; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.LocalServices; import com.android.server.SystemConfig; -import com.android.server.pm.parsing.PackageParser2; import com.android.server.rollback.RollbackManagerInternal; import java.io.File; @@ -399,7 +400,7 @@ final class PackageSessionVerifier { try (PackageParser2 packageParser = mPackageParserSupplier.get()) { File apexFile = new File(apexInfo.modulePath); parsedPackage = packageParser.parsePackage(apexFile, 0, false); - } catch (PackageManagerException e) { + } catch (PackageParserException e) { throw new PackageManagerException( PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e); diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java index 1089ac943802..051163925b16 100644 --- a/services/core/java/com/android/server/pm/ParallelPackageParser.java +++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java @@ -22,9 +22,10 @@ import android.os.Process; import android.os.Trace; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.PackageParser2; +import com.android.internal.pm.parsing.PackageParserException; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.util.ConcurrentUtils; -import com.android.server.pm.parsing.PackageParser2; import java.io.File; import java.util.concurrent.ArrayBlockingQueue; @@ -125,6 +126,10 @@ class ParallelPackageParser { @VisibleForTesting protected ParsedPackage parsePackage(File scanFile, int parseFlags) throws PackageManagerException { - return mPackageParser.parsePackage(scanFile, parseFlags, true); + try { + return mPackageParser.parsePackage(scanFile, parseFlags, true); + } catch (PackageParserException e) { + throw new PackageManagerException(e.error, e.getMessage(), e); + } } } diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java index 79c9c8ec8b0b..b6267c499c07 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java +++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java @@ -28,6 +28,7 @@ import android.system.StructStat; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.IPackageCacher; import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.ApexManager; @@ -39,7 +40,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; -public class PackageCacher { +public class PackageCacher implements IPackageCacher { private static final String TAG = "PackageCacher"; @@ -162,6 +163,7 @@ public class PackageCacher { * Returns the cached parse result for {@code packageFile} for parse flags {@code flags}, * or {@code null} if no cached result exists. */ + @Override public ParsedPackage getCachedResult(File packageFile, int flags) { final String cacheKey = getCacheKey(packageFile, flags); final File cacheFile = new File(mCacheDir, cacheKey); @@ -192,6 +194,7 @@ public class PackageCacher { /** * Caches the parse result for {@code packageFile} with flags {@code flags}. */ + @Override public void cacheResult(File packageFile, int flags, ParsedPackage parsed) { try { final String cacheKey = getCacheKey(packageFile, flags); diff --git a/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java b/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java new file mode 100644 index 000000000000..03a7a37a550c --- /dev/null +++ b/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java @@ -0,0 +1,79 @@ +/* + * 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.pm.parsing; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.ServiceManager; +import android.util.Slog; + +import com.android.internal.compat.IPlatformCompat; +import com.android.internal.pm.parsing.PackageParser2; +import com.android.internal.pm.pkg.parsing.ParsingUtils; +import com.android.server.SystemConfig; +import com.android.server.pm.PackageManagerService; + +import java.util.Set; + +public class PackageParserUtils { + /** + * For parsing inside the system server but outside of {@link PackageManagerService}. + * Generally used for parsing information in an APK that hasn't been installed yet. + * + * This must be called inside the system process as it relies on {@link ServiceManager}. + */ + @NonNull + @SuppressLint("AndroidFrameworkRequiresPermission") + public static PackageParser2 forParsingFileWithDefaults() { + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + return new PackageParser2(null /* separateProcesses */, null /* displayMetrics */, + null /* cacheDir */, new PackageParser2.Callback() { + @Override + @SuppressLint("AndroidFrameworkRequiresPermission") + public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) { + try { + return platformCompat.isChangeEnabled(changeId, appInfo); + } catch (Exception e) { + // This shouldn't happen, but assume enforcement if it does + Slog.wtf(ParsingUtils.TAG, "IPlatformCompat query failed", e); + return true; + } + } + + @Override + public boolean hasFeature(String feature) { + // Assume the device doesn't support anything. This will affect permission parsing + // and will force <uses-permission/> declarations to include all requiredNotFeature + // permissions and exclude all requiredFeature permissions. This mirrors the old + // behavior. + return false; + } + + @Override + public Set<String> getHiddenApiWhitelistedApps() { + return SystemConfig.getInstance().getHiddenApiWhitelistedApps(); + } + + @Override + public Set<String> getInstallConstraintsAllowlist() { + return SystemConfig.getInstance().getInstallConstraintsAllowlist(); + } + }); + } +} diff --git a/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java index b531b0eff854..611e4ed0e1f4 100644 --- a/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java +++ b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java @@ -76,6 +76,18 @@ class DeferredKeyActionExecutor { getActionsBufferWithLazyCleanUp(keyCode, downTime).setExecutable(); } + /** + * Clears all the queued action for given key code. + * + * @param keyCode the key code whose queued actions will be cleared. + */ + public void cancelQueuedAction(int keyCode) { + TimedActionsBuffer actionsBuffer = mBuffers.get(keyCode); + if (actionsBuffer != null) { + actionsBuffer.clear(); + } + } + private TimedActionsBuffer getActionsBufferWithLazyCleanUp(int keyCode, long downTime) { TimedActionsBuffer buffer = mBuffers.get(keyCode); if (buffer == null || buffer.getDownTime() != downTime) { @@ -146,6 +158,10 @@ class DeferredKeyActionExecutor { mActions.clear(); } + void clear() { + mActions.clear(); + } + void dump(String prefix, PrintWriter pw) { if (mExecutable) { pw.println(prefix + " " + KeyEvent.keyCodeToString(mKeyCode) + ": executable"); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 938ed2329ffd..3000a1c5c043 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -103,6 +103,7 @@ import android.app.ActivityManager; import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; +import android.app.ActivityTaskManager.RootTaskInfo; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IUiModeManager; @@ -584,6 +585,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { private int mLongPressOnStemPrimaryBehavior; private RecentTaskInfo mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp; + // The focused task at the time when the first STEM_PRIMARY key was released. This can only + // be accessed from the looper thread. + private RootTaskInfo mFocusedTaskInfoOnStemPrimarySingleKeyUp; + private boolean mHandleVolumeKeysInWM; private boolean mPendingKeyguardOccluded; @@ -2135,12 +2140,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { static class Injector { private final Context mContext; private final WindowManagerFuncs mWindowManagerFuncs; - private final Looper mLooper; - Injector(Context context, WindowManagerFuncs funcs, Looper looper) { + Injector(Context context, WindowManagerFuncs funcs) { mContext = context; mWindowManagerFuncs = funcs; - mLooper = looper; } Context getContext() { @@ -2152,7 +2155,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } Looper getLooper() { - return mLooper; + return Looper.myLooper(); } AccessibilityShortcutController getAccessibilityShortcutController( @@ -2195,7 +2198,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ @Override public void init(Context context, WindowManagerFuncs funcs) { - init(new Injector(context, funcs, Looper.myLooper())); + init(new Injector(context, funcs)); } @VisibleForTesting @@ -2723,7 +2726,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override void onPress(long downTime, int unusedDisplayId) { - if (mShouldEarlyShortPressOnStemPrimary) { + if (shouldHandleStemPrimaryEarlyShortPress()) { return; } // Short-press should be triggered only if app doesn't handle it. @@ -2747,6 +2750,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (count == 3 && mTriplePressOnStemPrimaryBehavior == TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY) { + // Cancel any queued actions for current key code to prevent them from being + // launched after a11y layer enabled. If the action happens early, + // undoEarlySinglePress will make sure the correct task is on top. + mDeferredKeyActionExecutor.cancelQueuedAction(KeyEvent.KEYCODE_STEM_PRIMARY); + undoEarlySinglePress(); stemPrimaryPress(count); } else { // Other multi-press gestures should be triggered only if app doesn't handle it. @@ -2755,6 +2763,27 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + /** + * This method undo the previously launched early-single-press action by bringing the + * focused task before launching early-single-press back to top. + */ + private void undoEarlySinglePress() { + if (shouldHandleStemPrimaryEarlyShortPress() + && mFocusedTaskInfoOnStemPrimarySingleKeyUp != null) { + try { + mActivityManagerService.startActivityFromRecents( + mFocusedTaskInfoOnStemPrimarySingleKeyUp.taskId, null); + } catch (RemoteException | IllegalArgumentException e) { + Slog.e( + TAG, + "Failed to start task " + + mFocusedTaskInfoOnStemPrimarySingleKeyUp.taskId + + " from recents", + e); + } + } + } + @Override void onKeyUp(long eventTime, int count, int unusedDisplayId) { if (count == 1) { @@ -2763,15 +2792,49 @@ public class PhoneWindowManager implements WindowManagerPolicy { // It is possible that we may navigate away from this task before the double // press is detected, as a result of the first press, so we save the current // most recent task before that happens. + // TODO(b/311497918): guard this with DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp = mActivityTaskManagerInternal.getMostRecentTaskFromBackground(); - if (mShouldEarlyShortPressOnStemPrimary) { + + mFocusedTaskInfoOnStemPrimarySingleKeyUp = null; + + if (shouldHandleStemPrimaryEarlyShortPress()) { // Key-up gesture should be triggered only if app doesn't handle it. mDeferredKeyActionExecutor.queueKeyAction( - KeyEvent.KEYCODE_STEM_PRIMARY, eventTime, () -> stemPrimaryPress(1)); + KeyEvent.KEYCODE_STEM_PRIMARY, + eventTime, + () -> { + // Save the info of the focused task on screen. This may be used + // later to bring the current focused task back to top. For + // example, stem primary triple press enables the A11y interface + // on top of the current focused task. When early single press is + // enabled for stem primary, the focused task could change to + // something else upon first key up event. In that case, we will + // bring the task recorded by this variable back to top. Then, start + // A11y interface. + try { + mFocusedTaskInfoOnStemPrimarySingleKeyUp = + mActivityManagerService.getFocusedRootTaskInfo(); + } catch (RemoteException e) { + Slog.e( + TAG, + "StemPrimaryKeyRule: onKeyUp: error while getting " + + "focused task " + + "info.", + e); + } + + stemPrimaryPress(1); + }); } } } + + // TODO(b/311497918): make a shouldHandlePowerEarlyShortPress for power button. + private boolean shouldHandleStemPrimaryEarlyShortPress() { + return mShouldEarlyShortPressOnStemPrimary + && mShortPressOnStemPrimaryBehavior == SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS; + } } private void initSingleKeyGestureRules(Looper looper) { @@ -6865,13 +6928,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { static class ButtonOverridePermissionChecker { boolean canAppOverrideSystemKey(Context context, int uid) { return PermissionChecker.checkPermissionForDataDelivery( - context, - OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW, - PID_UNKNOWN, - uid, - null, - null, - null) + context, + OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW, + PID_UNKNOWN, + uid, + null, + null, + null) == PERMISSION_GRANTED; } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 1ce87a7b6af3..145eb3b8464c 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -125,6 +125,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_OLD_UNSET; import static android.view.WindowManager.TRANSIT_RELAUNCH; +import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; @@ -683,6 +684,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults = new WindowState.UpdateReportedVisibilityResults(); + // TODO(b/317000737): Replace it with visibility states lookup. int mTransitionChangeFlags; /** Whether we need to setup the animation to animate only within the letterbox. */ @@ -5468,8 +5470,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Defer committing visibility until transition starts. if (isCollecting) { // It may be occluded by the activity above that calls convertFromTranslucent(). - if (!visible && mTransitionController.inPlayingTransition(this)) { - mTransitionChangeFlags |= FLAG_IS_OCCLUDED; + // Or it may be restoring transient launch to invisible when finishing transition. + if (!visible) { + if (mTransitionController.inPlayingTransition(this)) { + mTransitionChangeFlags |= FLAG_IS_OCCLUDED; + } else if (mTransitionController.inFinishingTransition(this)) { + mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION; + } } return; } diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index 1b45c1b4f3f1..e7621ffe8e3c 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -224,7 +224,7 @@ class ActivityStartInterceptor { // before issuing the work challenge. return true; } - if (interceptLockedManagedProfileIfNeeded()) { + if (interceptLockedProfileIfNeeded()) { return true; } if (interceptHomeIfNeeded()) { @@ -378,7 +378,7 @@ class ActivityStartInterceptor { return true; } - private boolean interceptLockedManagedProfileIfNeeded() { + private boolean interceptLockedProfileIfNeeded() { final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId); if (interceptingIntent == null) { return false; diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index b5e5d848c75c..cb2adbcf460a 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -579,30 +579,11 @@ class ActivityStarter { computeResolveFilterUid(callingUid, realCallingUid, filterCallingUid), realCallingPid); if (resolveInfo == null) { - final UserInfo userInfo = supervisor.getUserInfo(userId); - if (userInfo != null && userInfo.isManagedProfile()) { - // Special case for managed profiles, if attempting to launch non-cryto aware - // app in a locked managed profile from an unlocked parent allow it to resolve - // as user will be sent via confirm credentials to unlock the profile. - final UserManager userManager = UserManager.get(supervisor.mService.mContext); - boolean profileLockedAndParentUnlockingOrUnlocked = false; - final long token = Binder.clearCallingIdentity(); - try { - final UserInfo parent = userManager.getProfileParent(userId); - profileLockedAndParentUnlockingOrUnlocked = (parent != null) - && userManager.isUserUnlockingOrUnlocked(parent.id) - && !userManager.isUserUnlockingOrUnlocked(userId); - } finally { - Binder.restoreCallingIdentity(token); - } - if (profileLockedAndParentUnlockingOrUnlocked) { - resolveInfo = supervisor.resolveIntent(intent, resolvedType, userId, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - computeResolveFilterUid(callingUid, realCallingUid, - filterCallingUid), realCallingPid); - } - } + // Special case for profiles: If attempting to launch non-crypto aware app in a + // locked profile or launch an app in a profile that is stopped by quiet mode from + // an unlocked parent, allow it to resolve as user will be sent via confirm + // credentials to unlock the profile. + resolveInfo = resolveIntentForLockedOrStoppedProfiles(supervisor); } // Collect information about the target of the Intent. @@ -616,6 +597,36 @@ class ActivityStarter { UserHandle.getUserId(activityInfo.applicationInfo.uid)); } } + + /** + * Resolve intent for locked or stopped profiles if the parent profile is unlocking or + * unlocked. + */ + ResolveInfo resolveIntentForLockedOrStoppedProfiles( + ActivityTaskSupervisor supervisor) { + final UserInfo userInfo = supervisor.getUserInfo(userId); + if (userInfo != null && userInfo.isProfile()) { + final UserManager userManager = UserManager.get(supervisor.mService.mContext); + boolean profileLockedAndParentUnlockingOrUnlocked = false; + final long token = Binder.clearCallingIdentity(); + try { + final UserInfo parent = userManager.getProfileParent(userId); + profileLockedAndParentUnlockingOrUnlocked = (parent != null) + && userManager.isUserUnlockingOrUnlocked(parent.id) + && !userManager.isUserUnlockingOrUnlocked(userId); + } finally { + Binder.restoreCallingIdentity(token); + } + if (profileLockedAndParentUnlockingOrUnlocked) { + return supervisor.resolveIntent(intent, resolvedType, userId, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + computeResolveFilterUid(callingUid, realCallingUid, + filterCallingUid), realCallingPid); + } + } + return null; + } } ActivityStarter(ActivityStartController controller, ActivityTaskManagerService service, diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 908c49eb8580..dbae29bd37c9 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -62,6 +62,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIV import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL; import static android.provider.Settings.Global.HIDE_ERROR_DIALOGS; import static android.provider.Settings.System.FONT_SCALE; +import static android.service.controls.flags.Flags.homePanelDream; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.TRANSIT_CHANGE; @@ -1501,14 +1502,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { a.exported = true; a.name = DreamActivity.class.getName(); a.enabled = true; - a.launchMode = ActivityInfo.LAUNCH_SINGLE_INSTANCE; a.persistableMode = ActivityInfo.PERSIST_NEVER; a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT; a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; - a.resizeMode = RESIZE_MODE_UNRESIZEABLE; a.configChanges = 0xffffffff; + if (homePanelDream()) { + a.launchMode = ActivityInfo.LAUNCH_SINGLE_TASK; + } else { + a.resizeMode = RESIZE_MODE_UNRESIZEABLE; + a.launchMode = ActivityInfo.LAUNCH_SINGLE_INSTANCE; + } + final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchActivityType(ACTIVITY_TYPE_DREAM); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f020bfa8cbc7..3117db5f27f0 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -43,6 +43,7 @@ import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; +import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; @@ -3067,6 +3068,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { Slog.e(TAG, "Unexpected launch-task-behind operation in shell transition"); flags |= FLAG_TASK_LAUNCHING_BEHIND; } + if ((topActivity.mTransitionChangeFlags & FLAGS_IS_OCCLUDED_NO_ANIMATION) + == FLAGS_IS_OCCLUDED_NO_ANIMATION) { + flags |= FLAGS_IS_OCCLUDED_NO_ANIMATION; + } } if (task.voiceSession != null) { flags |= FLAG_IS_VOICE_INTERACTION; diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS index 061fe0fc88d9..cc08488742b2 100644 --- a/services/core/jni/OWNERS +++ b/services/core/jni/OWNERS @@ -31,5 +31,5 @@ per-file com_android_server_vibrator_* = file:/services/core/java/com/android/se per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS -# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java -per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS +# Bug component : 158088 = per-file *AnrTimer* +per-file *AnrTimer* = file:/PERFORMANCE_OWNERS diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index c625b1e1eef7..807874522b8c 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -595,7 +595,7 @@ <!-- Sets the brightness mapping of the desired screen brightness to the corresponding lux for the current display --> <xs:element name="luxToBrightnessMapping" type="luxToBrightnessMapping" - minOccurs="0" maxOccurs="1"> + minOccurs="0" maxOccurs="unbounded"> <xs:annotation name="final"/> </xs:element> </xs:sequence> @@ -619,12 +619,20 @@ This is used in place of config_autoBrightnessLevels and config_autoBrightnessLcdBacklightValues defined in the config XML resource. + + On devices that allow users to choose from a set of predefined options in display + auto-brightness settings, multiple mappings for different modes and settings can be defined. + + If no mode is specified, the mapping will be used for the default mode. + If no setting is specified, the mapping will be used for the normal brightness setting. --> <xs:complexType name="luxToBrightnessMapping"> <xs:element name="map" type="nonNegativeFloatToFloatMap"> <xs:annotation name="nonnull"/> <xs:annotation name="final"/> </xs:element> + <xs:element name="mode" type="AutoBrightnessModeName" minOccurs="0"/> + <xs:element name="setting" type="xs:string" minOccurs="0"/> </xs:complexType> <!-- Represents a point in the display brightness mapping, representing the lux level from the @@ -757,4 +765,14 @@ </xs:element> </xs:sequence> </xs:complexType> + + <!-- Predefined type names as defined by + AutomaticBrightnessController.AutomaticBrightnessMode --> + <xs:simpleType name="AutoBrightnessModeName"> + <xs:restriction base="xs:string"> + <xs:enumeration value="default"/> + <xs:enumeration value="idle"/> + <xs:enumeration value="doze"/> + </xs:restriction> + </xs:simpleType> </xs:schema> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 8c8c1230f944..91172a334bb1 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -8,13 +8,19 @@ package com.android.server.display.config { method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis(); method public final java.math.BigInteger getDarkeningLightDebounceMillis(); method public boolean getEnabled(); - method public final com.android.server.display.config.LuxToBrightnessMapping getLuxToBrightnessMapping(); + method public final java.util.List<com.android.server.display.config.LuxToBrightnessMapping> getLuxToBrightnessMapping(); method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger); method public final void setBrighteningLightDebounceMillis(java.math.BigInteger); method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger); method public final void setDarkeningLightDebounceMillis(java.math.BigInteger); method public void setEnabled(boolean); - method public final void setLuxToBrightnessMapping(com.android.server.display.config.LuxToBrightnessMapping); + } + + public enum AutoBrightnessModeName { + method public String getRawName(); + enum_constant public static final com.android.server.display.config.AutoBrightnessModeName _default; + enum_constant public static final com.android.server.display.config.AutoBrightnessModeName doze; + enum_constant public static final com.android.server.display.config.AutoBrightnessModeName idle; } public class BlockingZoneConfig { @@ -220,7 +226,11 @@ package com.android.server.display.config { public class LuxToBrightnessMapping { ctor public LuxToBrightnessMapping(); method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap(); + method public com.android.server.display.config.AutoBrightnessModeName getMode(); + method public String getSetting(); method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap); + method public void setMode(com.android.server.display.config.AutoBrightnessModeName); + method public void setSetting(String); } public class NitsMap { diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java index 03e45a27390f..71f5c754f22f 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java @@ -58,6 +58,8 @@ import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.PackageParser2; +import com.android.internal.pm.parsing.PackageParserException; import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.permission.CompatibilityPermissionInfo; @@ -84,7 +86,7 @@ import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.internal.util.ArrayUtils; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageInfoUtils; -import com.android.server.pm.parsing.PackageParser2; +import com.android.server.pm.parsing.PackageParserUtils; import com.android.server.pm.parsing.TestPackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; @@ -185,7 +187,7 @@ public class PackageParserTest { @Test public void test_serializePackage() throws Exception { - try (PackageParser2 pp = PackageParser2.forParsingFileWithDefaults()) { + try (PackageParser2 pp = PackageParserUtils.forParsingFileWithDefaults()) { AndroidPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, true /* useCaches */).hideAsFinal(); @@ -363,7 +365,7 @@ public class PackageParserTest { actualDisplayCategory = activity.getRequiredDisplayCategory(); } } - } catch (PackageManagerException e) { + } catch (PackageParserException e) { assertThat(e.getMessage()).contains( "requiredDisplayCategory attribute can only consist" + " of alphanumeric characters, '_', and '.'"); diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java index 8a74e24a3810..376124049c4c 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java @@ -21,8 +21,8 @@ import android.util.Log; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.PackageParser2; import com.android.internal.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.TestPackageParser2; import junit.framework.Assert; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java index b63950c10a18..a28b28a49162 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java @@ -38,6 +38,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.PackageParserException; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedActivityUtils; import com.android.internal.pm.pkg.component.ParsedComponent; @@ -45,7 +46,6 @@ import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.internal.pm.pkg.component.ParsedPermissionUtils; import com.android.internal.util.ArrayUtils; -import com.android.server.pm.PackageManagerException; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.test.service.server.R; @@ -608,7 +608,7 @@ public class PackageParserLegacyCoreTest { try { parsePackage(filename, resId, x -> x); expect.withMessage("Expected parsing error %s from %s", result, filename).fail(); - } catch (PackageManagerException expected) { + } catch (PackageParserException expected) { expect.that(expected.error).isEqualTo(result); } } diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt index 98af63c65e90..1d668cd3e8f2 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt @@ -18,8 +18,8 @@ package com.android.server.pm.parsing import android.content.pm.PackageManager import android.platform.test.annotations.Postsubmit +import com.android.internal.pm.parsing.PackageParserException import com.android.internal.pm.pkg.parsing.ParsingPackageUtils -import com.android.server.pm.PackageManagerException import com.android.server.pm.PackageManagerService import com.android.server.pm.PackageManagerServiceUtils import java.io.File @@ -39,7 +39,7 @@ import org.junit.rules.TemporaryFolder @Postsubmit class SystemPartitionParseTest { - private val parser = PackageParser2.forParsingFileWithDefaults() + private val parser = PackageParserUtils.forParsingFileWithDefaults() @get:Rule val tempFolder = TemporaryFolder() @@ -86,7 +86,7 @@ class SystemPartitionParseTest { } } .mapNotNull { it.exceptionOrNull() } - .filterNot { (it as? PackageManagerException)?.error == + .filterNot { (it as? PackageParserException)?.error == PackageManager.INSTALL_PARSE_FAILED_SKIPPED } if (exceptions.isEmpty()) return diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java index 189d9bbfe806..f4eaa5b7046c 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -107,20 +107,6 @@ public class BrightnessMappingStrategyTest { 468.5f, }; - private static final int[] DISPLAY_LEVELS_INT = { - 9, - 30, - 45, - 62, - 78, - 96, - 119, - 146, - 178, - 221, - 255 - }; - private static final float[] DISPLAY_LEVELS = { 0.03f, 0.11f, @@ -172,62 +158,23 @@ public class BrightnessMappingStrategyTest { DisplayWhiteBalanceController mMockDwbc; @Test - public void testSimpleStrategyMappingAtControlPoints_IntConfig() { - Resources res = createResources(DISPLAY_LEVELS_INT); - DisplayDeviceConfig ddc = createDdc(); + public void testSimpleStrategyMappingAtControlPoints() { + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build(); BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, - mMockDwbc); - assertNotNull("BrightnessMappingStrategy should not be null", simple); - for (int i = 0; i < LUX_LEVELS.length; i++) { - final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1, - PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN, - PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_INT[i]); - assertEquals(expectedLevel, - simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/); - } - } - - @Test - public void testSimpleStrategyMappingBetweenControlPoints_IntConfig() { - Resources res = createResources(DISPLAY_LEVELS_INT); - DisplayDeviceConfig ddc = createDdc(); - BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, - mMockDwbc); - assertNotNull("BrightnessMappingStrategy should not be null", simple); - for (int i = 1; i < LUX_LEVELS.length; i++) { - final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2; - final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON; - assertTrue("Desired brightness should be between adjacent control points.", - backlight > DISPLAY_LEVELS_INT[i - 1] - && backlight < DISPLAY_LEVELS_INT[i]); - } - } - - @Test - public void testSimpleStrategyMappingAtControlPoints_FloatConfig() { - Resources res = createResources(EMPTY_INT_ARRAY); - DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS, - EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS); - BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, - mMockDwbc); + AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertNotNull("BrightnessMappingStrategy should not be null", simple); for (int i = 0; i < LUX_LEVELS.length; i++) { - assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), - /* tolerance= */ 0.0001f); + assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), TOLERANCE); } } @Test - public void testSimpleStrategyMappingBetweenControlPoints_FloatConfig() { - Resources res = createResources(EMPTY_INT_ARRAY); - DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS, - EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS); + public void testSimpleStrategyMappingBetweenControlPoints() { + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build(); BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, - mMockDwbc); + AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertNotNull("BrightnessMappingStrategy should not be null", simple); for (int i = 1; i < LUX_LEVELS.length; i++) { final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2; @@ -239,8 +186,8 @@ public class BrightnessMappingStrategyTest { @Test public void testSimpleStrategyIgnoresNewConfiguration() { - Resources res = createResources(DISPLAY_LEVELS_INT); - DisplayDeviceConfig ddc = createDdc(); + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); @@ -255,25 +202,23 @@ public class BrightnessMappingStrategyTest { @Test public void testSimpleStrategyIgnoresNullConfiguration() { - Resources res = createResources(DISPLAY_LEVELS_INT); - DisplayDeviceConfig ddc = createDdc(); + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); strategy.setBrightnessConfiguration(null); - final int n = DISPLAY_LEVELS_INT.length; - final float expectedBrightness = - (float) DISPLAY_LEVELS_INT[n - 1] / PowerManager.BRIGHTNESS_ON; + final int n = DISPLAY_LEVELS.length; + final float expectedBrightness = DISPLAY_LEVELS[n - 1]; assertEquals(expectedBrightness, strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/); } @Test public void testPhysicalStrategyMappingAtControlPoints() { - Resources res = createResources(EMPTY_INT_ARRAY); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, - DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, - LUX_LEVELS, DISPLAY_LEVELS_NITS); + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS) + .build(); BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertNotNull("BrightnessMappingStrategy should not be null", physical); @@ -290,9 +235,9 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyMappingBetweenControlPoints() { - Resources res = createResources(EMPTY_INT_ARRAY); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE, - LUX_LEVELS, DISPLAY_LEVELS_NITS); + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE) + .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build(); BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertNotNull("BrightnessMappingStrategy should not be null", physical); @@ -309,9 +254,9 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyUsesNewConfigurations() { - Resources res = createResources(EMPTY_INT_ARRAY); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, - DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS); + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS) + .build(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); @@ -336,9 +281,9 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyRecalculateSplines() { - Resources res = createResources(EMPTY_INT_ARRAY); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, - DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS); + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS) + .build(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length]; @@ -381,9 +326,10 @@ public class BrightnessMappingStrategyTest { @Test public void testDefaultStrategyIsPhysical() { - Resources res = createResources(DISPLAY_LEVELS_INT); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, - DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS); + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS) + .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS) + .build(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy); @@ -396,17 +342,17 @@ public class BrightnessMappingStrategyTest { float tmp = lux[idx]; lux[idx] = lux[idx + 1]; lux[idx + 1] = tmp; - Resources res = createResources(EMPTY_INT_ARRAY); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, - DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS); + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux) + .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertNull(strategy); // And make sure we get the same result even if it's monotone but not increasing. lux[idx] = lux[idx + 1]; - ddc = createDdc(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, - DISPLAY_LEVELS_NITS); + ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux) + .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build(); strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertNull(strategy); @@ -419,25 +365,25 @@ public class BrightnessMappingStrategyTest { // Make sure it's strictly increasing so that the only failure is the differing array // lengths lux[lux.length - 1] = lux[lux.length - 2] + 1; - Resources res = createResources(EMPTY_INT_ARRAY); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, - DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS); + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux) + .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertNull(strategy); - res = createResources(DISPLAY_LEVELS_INT); + ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux) + .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS) + .setAutoBrightnessLevels(DISPLAY_LEVELS).build(); strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertNull(strategy); // Extra backlight level - final int[] backlight = Arrays.copyOf( - DISPLAY_LEVELS_INT, DISPLAY_LEVELS_INT.length + 1); + final float[] backlight = Arrays.copyOf(DISPLAY_LEVELS, DISPLAY_LEVELS.length + 1); backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1; - res = createResources(backlight); - ddc = createDdc(DISPLAY_RANGE_NITS, - DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, EMPTY_FLOAT_ARRAY); + res = createResources(); + ddc = new DdcBuilder().setAutoBrightnessLevels(backlight).build(); strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertNull(strategy); @@ -445,9 +391,9 @@ public class BrightnessMappingStrategyTest { // Extra nits level final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1); nits[nits.length - 1] = nits[nits.length - 2] + 1; - res = createResources(EMPTY_INT_ARRAY); - ddc = createDdc(DISPLAY_RANGE_NITS, - DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, nits); + res = createResources(); + ddc = new DdcBuilder().setAutoBrightnessLevelsNits(nits) + .setAutoBrightnessLevels(EMPTY_FLOAT_ARRAY).build(); strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertNull(strategy); @@ -455,40 +401,32 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyRequiresNitsMapping() { - Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/); - DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/); + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setNitsRange(EMPTY_FLOAT_ARRAY).build(); BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertNull(physical); - - res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/); - physical = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, - mMockDwbc); - assertNull(physical); - - res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/); - physical = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, - mMockDwbc); - assertNull(physical); } @Test public void testStrategiesAdaptToUserDataPoint() { - Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE, - LUX_LEVELS, DISPLAY_LEVELS_NITS); + Resources res = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE) + .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build(); assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc)); - ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); - res = createResources(DISPLAY_LEVELS_INT); + ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE) + .setAutoBrightnessLevels(DISPLAY_LEVELS).build(); + res = createResources(); assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc)); } @Test public void testIdleModeConfigLoadsCorrectly() { - Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); + Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE); + DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE) + .build(); // Create an idle mode bms // This will fail if it tries to fetch the wrong configuration. @@ -562,17 +500,11 @@ public class BrightnessMappingStrategyTest { assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/); } - private Resources createResources(int[] brightnessLevelsBacklight) { - return createResources(brightnessLevelsBacklight, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY); - } - - private Resources createResourcesIdle(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) { - return createResources(EMPTY_INT_ARRAY, - luxLevelsIdle, brightnessLevelsNitsIdle); + private Resources createResources() { + return createResources(EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY); } - private Resources createResources(int[] brightnessLevelsBacklight, int[] luxLevelsIdle, - float[] brightnessLevelsNitsIdle) { + private Resources createResources(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) { Resources mockResources = mock(Resources.class); if (luxLevelsIdle.length > 0) { @@ -583,10 +515,6 @@ public class BrightnessMappingStrategyTest { .thenReturn(luxLevelsIdleResource); } - when(mockResources.getIntArray( - com.android.internal.R.array.config_autoBrightnessLcdBacklightValues)) - .thenReturn(brightnessLevelsBacklight); - TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle); when(mockResources.obtainTypedArray( com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle)) @@ -604,41 +532,6 @@ public class BrightnessMappingStrategyTest { return mockResources; } - private DisplayDeviceConfig createDdc() { - return createDdc(DISPLAY_RANGE_NITS); - } - - private DisplayDeviceConfig createDdc(float[] nitsArray) { - return createDdc(nitsArray, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT); - } - - private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray) { - DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class); - when(mockDdc.getNits()).thenReturn(nitsArray); - when(mockDdc.getBrightness()).thenReturn(backlightArray); - when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS); - when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY); - when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(EMPTY_FLOAT_ARRAY); - return mockDdc; - } - - private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray, - float[] luxLevelsFloat, float[] brightnessLevelsNits) { - return createDdc(nitsArray, backlightArray, luxLevelsFloat, brightnessLevelsNits, - EMPTY_FLOAT_ARRAY); - } - - private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray, - float[] luxLevelsFloat, float[] brightnessLevelsNits, float[] brightnessLevels) { - DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class); - when(mockDdc.getNits()).thenReturn(nitsArray); - when(mockDdc.getBrightness()).thenReturn(backlightArray); - when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevelsFloat); - when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits); - when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(brightnessLevels); - return mockDdc; - } - private TypedArray createFloatTypedArray(float[] vals) { TypedArray mockArray = mock(TypedArray.class); when(mockArray.length()).thenAnswer(invocation -> { @@ -677,10 +570,9 @@ public class BrightnessMappingStrategyTest { final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3); - Resources resources = createResources(EMPTY_INT_ARRAY); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, - DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX, - GAMMA_CORRECTION_NITS); + Resources resources = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX) + .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); // Let's start with a validity check: @@ -708,10 +600,9 @@ public class BrightnessMappingStrategyTest { final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1); final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3); - Resources resources = createResources(EMPTY_INT_ARRAY); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, - DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX, - GAMMA_CORRECTION_NITS); + Resources resources = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX) + .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); // Validity check: @@ -736,10 +627,9 @@ public class BrightnessMappingStrategyTest { public void testGammaCorrectionExtremeChangeAtCenter() { // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we // just make sure the adjustment reflects the change. - Resources resources = createResources(EMPTY_INT_ARRAY); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, - DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX, - GAMMA_CORRECTION_NITS); + Resources resources = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX) + .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f); @@ -760,10 +650,9 @@ public class BrightnessMappingStrategyTest { final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0); final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4); - Resources resources = createResources(EMPTY_INT_ARRAY); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, - DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX, - GAMMA_CORRECTION_NITS); + Resources resources = createResources(); + DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX) + .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); // Validity, as per tradition: @@ -790,11 +679,58 @@ public class BrightnessMappingStrategyTest { @Test public void testGetMode() { - Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE); - DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); + Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE); + DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE) + .build(); BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_IDLE, mMockDwbc); assertEquals(AUTO_BRIGHTNESS_MODE_IDLE, strategy.getMode()); } + + private static class DdcBuilder { + private DisplayDeviceConfig mDdc; + + DdcBuilder() { + mDdc = mock(DisplayDeviceConfig.class); + when(mDdc.getNits()).thenReturn(DISPLAY_RANGE_NITS); + when(mDdc.getBrightness()).thenReturn(DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT); + when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT)) + .thenReturn(LUX_LEVELS); + when(mDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY); + when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT)) + .thenReturn(EMPTY_FLOAT_ARRAY); + } + + DdcBuilder setNitsRange(float[] nitsArray) { + when(mDdc.getNits()).thenReturn(nitsArray); + return this; + } + + DdcBuilder setBrightnessRange(float[] brightnessArray) { + when(mDdc.getBrightness()).thenReturn(brightnessArray); + return this; + } + + DdcBuilder setAutoBrightnessLevelsLux(float[] luxLevels) { + when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT)) + .thenReturn(luxLevels); + return this; + } + + DdcBuilder setAutoBrightnessLevelsNits(float[] brightnessLevelsNits) { + when(mDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits); + return this; + } + + DdcBuilder setAutoBrightnessLevels(float[] brightnessLevels) { + when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT)) + .thenReturn(brightnessLevels); + return this; + } + + DisplayDeviceConfig build() { + return mDdc; + } + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index 31d7e88e671b..61c60765c966 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -17,6 +17,8 @@ package com.android.server.display; +import static com.android.internal.display.BrightnessSynchronizer.brightnessIntToFloat; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; import static com.android.server.display.config.SensorData.SupportedMode; import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat; import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat; @@ -47,7 +49,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; -import com.android.internal.display.BrightnessSynchronizer; import com.android.server.display.config.HdrBrightnessData; import com.android.server.display.config.ThermalStatus; import com.android.server.display.feature.DisplayManagerFlags; @@ -605,10 +606,13 @@ public final class DisplayDeviceConfigTest { private void verifyConfigValuesFromConfigResource() { assertNull(mDisplayDeviceConfig.getName()); - assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new - float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA); - assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new - float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA); + assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), + new float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA); + assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux( + AUTO_BRIGHTNESS_MODE_DEFAULT), new float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA); + assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels( + AUTO_BRIGHTNESS_MODE_DEFAULT), new float[]{brightnessIntToFloat(50), + brightnessIntToFloat(100), brightnessIntToFloat(150)}, SMALL_DELTA); // Test thresholds assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA); @@ -674,7 +678,7 @@ public final class DisplayDeviceConfigTest { assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type); assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name); - assertEquals(BrightnessSynchronizer.brightnessIntToFloat(35), + assertEquals(brightnessIntToFloat(35), mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA); } @@ -734,9 +738,32 @@ public final class DisplayDeviceConfigTest { getValidProxSensor(), /* includeIdleMode= */ false)); assertArrayEquals(new float[]{0.0f, 80}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux( + AUTO_BRIGHTNESS_MODE_DEFAULT), ZERO_DELTA); assertArrayEquals(new float[]{0.2f, 0.3f}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels( + AUTO_BRIGHTNESS_MODE_DEFAULT), SMALL_DELTA); + + assertArrayEquals(new float[]{0.0f, 90}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("default", "dim"), + ZERO_DELTA); + assertArrayEquals(new float[]{0.3f, 0.4f}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("default", "dim"), + SMALL_DELTA); + + assertArrayEquals(new float[]{0.0f, 95}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "normal"), + ZERO_DELTA); + assertArrayEquals(new float[]{0.35f, 0.45f}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "normal"), + SMALL_DELTA); + + assertArrayEquals(new float[]{0.0f, 100}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "bright"), + ZERO_DELTA); + assertArrayEquals(new float[]{0.4f, 0.5f}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "bright"), + SMALL_DELTA); } @Test @@ -746,9 +773,13 @@ public final class DisplayDeviceConfigTest { setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(), getValidProxSensor(), /* includeIdleMode= */ false)); - assertNull(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels()); + assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100), + brightnessIntToFloat(150)}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels( + AUTO_BRIGHTNESS_MODE_DEFAULT), SMALL_DELTA); assertArrayEquals(new float[]{0, 110, 500}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux( + AUTO_BRIGHTNESS_MODE_DEFAULT), ZERO_DELTA); assertArrayEquals(new float[]{2, 200, 600}, mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), SMALL_DELTA); } @@ -1138,6 +1169,46 @@ public final class DisplayDeviceConfigTest { + "</point>\n" + "</map>\n" + "</luxToBrightnessMapping>\n" + + "<luxToBrightnessMapping>\n" + + "<setting>dim</setting>\n" + + "<map>\n" + + "<point>\n" + + "<first>0</first>\n" + + "<second>0.3</second>\n" + + "</point>\n" + + "<point>\n" + + "<first>90</first>\n" + + "<second>0.4</second>\n" + + "</point>\n" + + "</map>\n" + + "</luxToBrightnessMapping>\n" + + "<luxToBrightnessMapping>\n" + + "<mode>doze</mode>\n" + + "<map>\n" + + "<point>\n" + + "<first>0</first>\n" + + "<second>0.35</second>\n" + + "</point>\n" + + "<point>\n" + + "<first>95</first>\n" + + "<second>0.45</second>\n" + + "</point>\n" + + "</map>\n" + + "</luxToBrightnessMapping>\n" + + "<luxToBrightnessMapping>\n" + + "<mode>doze</mode>\n" + + "<setting>bright</setting>\n" + + "<map>\n" + + "<point>\n" + + "<first>0</first>\n" + + "<second>0.4</second>\n" + + "</point>\n" + + "<point>\n" + + "<first>100</first>\n" + + "<second>0.5</second>\n" + + "</point>\n" + + "</map>\n" + + "</luxToBrightnessMapping>\n" + "</autoBrightness>\n" + getPowerThrottlingConfig() + "<highBrightnessMode enabled=\"true\">\n" @@ -1435,6 +1506,10 @@ public final class DisplayDeviceConfigTest { when(mResources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevels)) .thenReturn(screenBrightnessLevelLux); + int[] screenBrightnessLevels = new int[]{50, 100, 150}; + when(mResources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLcdBacklightValues)) + .thenReturn(screenBrightnessLevels); // Thresholds // Config.xml requires the levels arrays to be of length N and the thresholds arrays to be diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java index 02bd35a5b17f..ffdc8b431c39 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java @@ -18,6 +18,8 @@ package com.android.server.display; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE; import static org.junit.Assert.assertNotNull; @@ -1568,6 +1570,56 @@ public final class DisplayPowerController2Test { eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } + @Test + public void testSwitchToDozeAutoBrightnessMode() { + when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true); + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_DOZE; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + // One triggered by handleBrightnessModeChange, another triggered by requestPowerState + verify(mHolder.automaticBrightnessController, times(2)) + .switchMode(AUTO_BRIGHTNESS_MODE_DOZE); + + // Back to default mode + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); + dpr.policy = DisplayPowerRequest.POLICY_BRIGHT; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT); + } + + @Test + public void testDoesNotSwitchFromIdleToDozeAutoBrightnessMode() { + when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true); + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE); + when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(mHolder.automaticBrightnessController, never()) + .switchMode(AUTO_BRIGHTNESS_MODE_DOZE); + } + + @Test + public void testDoesNotSwitchDozeAutoBrightnessModeIfFeatureFlagOff() { + when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(false); + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(mHolder.automaticBrightnessController, never()) + .switchMode(AUTO_BRIGHTNESS_MODE_DOZE); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ 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 0c845de8846e..00f98924eff3 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -206,6 +206,9 @@ public class LocalDisplayAdapterTest { when(mMockedResources.getIntArray( com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate)) .thenReturn(new int[]{}); + when(mMockedResources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLcdBacklightValues)) + .thenReturn(new int[]{}); doReturn(true).when(mFlags).isDisplayOffloadEnabled(); initDisplayOffloadSession(); } diff --git a/services/tests/media/OWNERS b/services/tests/media/OWNERS new file mode 100644 index 000000000000..160767a6427c --- /dev/null +++ b/services/tests/media/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 137631 +include platform/frameworks/av:/media/janitors/media_solutions_OWNERS diff --git a/services/tests/media/mediarouterservicetest/Android.bp b/services/tests/media/mediarouterservicetest/Android.bp new file mode 100644 index 000000000000..aed3af6b69f6 --- /dev/null +++ b/services/tests/media/mediarouterservicetest/Android.bp @@ -0,0 +1,39 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "MediaRouterServiceTests", + srcs: [ + "src/**/*.java", + ], + + static_libs: [ + "androidx.test.core", + "androidx.test.rules", + "androidx.test.runner", + "compatibility-device-util-axt", + "junit", + "platform-test-annotations", + "services.core", + "truth", + ], + + platform_apis: true, + + test_suites: [ + // "device-tests", + "general-tests", + ], + + certificate: "platform", + dxflags: ["--multi-dex"], + optimize: { + enabled: false, + }, +} diff --git a/services/tests/media/mediarouterservicetest/AndroidManifest.xml b/services/tests/media/mediarouterservicetest/AndroidManifest.xml new file mode 100644 index 000000000000..fe65f868bcb3 --- /dev/null +++ b/services/tests/media/mediarouterservicetest/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?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 xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.media.tests"> + + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> + <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" /> + + <application android:testOnly="true" android:debuggable="true"> + <uses-library android:name="android.test.runner"/> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.media.tests" + android:label="Frameworks Services Tests"/> +</manifest> diff --git a/services/tests/media/mediarouterservicetest/AndroidTest.xml b/services/tests/media/mediarouterservicetest/AndroidTest.xml new file mode 100644 index 000000000000..b0656816b701 --- /dev/null +++ b/services/tests/media/mediarouterservicetest/AndroidTest.xml @@ -0,0 +1,34 @@ +<?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. +--> +<configuration description="Runs MediaRouter Service tests."> + <option name="test-tag" value="MediaRouterServiceTests" /> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true"/> + <option name="test-file-name" value="MediaRouterServiceTests.apk"/> + <option name="install-arg" value="-t" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.InstrumentationTest" > + <option name="package" value="com.android.server.media.tests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java new file mode 100644 index 000000000000..6f9b6faa0bb0 --- /dev/null +++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java @@ -0,0 +1,341 @@ +/* + * 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.media; + +import static com.android.server.media.AudioRoutingUtils.ATTRIBUTES_MEDIA; +import static com.android.server.media.AudioRoutingUtils.getMediaAudioProductStrategy; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.content.Context; +import android.content.res.Resources; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceCallback; +import android.media.AudioDeviceInfo; +import android.media.AudioDevicePort; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.media.MediaRoute2Info; +import android.media.audiopolicy.AudioProductStrategy; +import android.os.Looper; +import android.os.UserHandle; + +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@RunWith(JUnit4.class) +public class AudioPoliciesDeviceRouteControllerTest { + + private static final String FAKE_ROUTE_NAME = "fake name"; + private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER = + createAudioDeviceInfo( + AudioSystem.DEVICE_OUT_SPEAKER, "name_builtin", /* address= */ null); + private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET = + createAudioDeviceInfo( + AudioSystem.DEVICE_OUT_WIRED_HEADSET, "name_wired_hs", /* address= */ null); + private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP = + createAudioDeviceInfo( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "name_a2dp", /* address= */ "12:34:45"); + + private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE = + createAudioDeviceInfo( + AudioSystem.DEVICE_OUT_EARPIECE, /* name= */ null, /* address= */ null); + + private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_NO_NAME = + createAudioDeviceInfo( + AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, + /* name= */ null, + /* address= */ null); + + private AudioDeviceInfo mSelectedAudioDeviceInfo; + private Set<AudioDeviceInfo> mAvailableAudioDeviceInfos; + @Mock private AudioManager mMockAudioManager; + @Mock private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; + private AudioPoliciesDeviceRouteController mControllerUnderTest; + private AudioDeviceCallback mAudioDeviceCallback; + private AudioProductStrategy mMediaAudioProductStrategy; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + Resources mockResources = Mockito.mock(Resources.class); + when(mockResources.getText(anyInt())).thenReturn(FAKE_ROUTE_NAME); + Context realContext = InstrumentationRegistry.getInstrumentation().getContext(); + Context mockContext = Mockito.mock(Context.class); + when(mockContext.getResources()).thenReturn(mockResources); + // The bluetooth stack needs the application info, but we cannot use a spy because the + // concrete class is package private, so we just return the application info through the + // mock. + when(mockContext.getApplicationInfo()).thenReturn(realContext.getApplicationInfo()); + + // Setup the initial state so that the route controller is created in a sensible state. + mSelectedAudioDeviceInfo = FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER; + mAvailableAudioDeviceInfos = Set.of(FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER); + updateMockAudioManagerState(); + mMediaAudioProductStrategy = getMediaAudioProductStrategy(); + + BluetoothAdapter btAdapter = + realContext.getSystemService(BluetoothManager.class).getAdapter(); + mControllerUnderTest = + new AudioPoliciesDeviceRouteController( + mockContext, + mMockAudioManager, + Looper.getMainLooper(), + mMediaAudioProductStrategy, + btAdapter, + mOnDeviceRouteChangedListener); + mControllerUnderTest.start(UserHandle.CURRENT_OR_SELF); + + ArgumentCaptor<AudioDeviceCallback> deviceCallbackCaptor = + ArgumentCaptor.forClass(AudioDeviceCallback.class); + verify(mMockAudioManager) + .registerAudioDeviceCallback(deviceCallbackCaptor.capture(), any()); + mAudioDeviceCallback = deviceCallbackCaptor.getValue(); + + // We clear any invocations during setup. + clearInvocations(mOnDeviceRouteChangedListener); + } + + @Test + public void getSelectedRoute_afterDevicesConnect_returnsRightSelectedRoute() { + assertThat(mControllerUnderTest.getSelectedRoute().getType()) + .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); + + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP); + verify(mOnDeviceRouteChangedListener).onDeviceRouteChanged(); + assertThat(mControllerUnderTest.getSelectedRoute().getType()) + .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); + + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ null, // Selected device doesn't change. + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET); + assertThat(mControllerUnderTest.getSelectedRoute().getType()) + .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); + } + + @Test + public void getSelectedRoute_afterDeviceRemovals_returnsExpectedRoutes() { + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP, + FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET); + verify(mOnDeviceRouteChangedListener).onDeviceRouteChanged(); + + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP); + verify(mOnDeviceRouteChangedListener, times(2)).onDeviceRouteChanged(); + assertThat(mControllerUnderTest.getSelectedRoute().getType()) + .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); + + removeAvailableAudioDeviceInfos( + /* newSelectedDevice= */ null, + /* devicesToRemove...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET); + assertThat(mControllerUnderTest.getSelectedRoute().getType()) + .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); + + removeAvailableAudioDeviceInfos( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER, + /* devicesToRemove...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET); + assertThat(mControllerUnderTest.getSelectedRoute().getType()) + .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); + } + + @Test + public void onAudioDevicesAdded_clearsAudioRoutingPoliciesCorrectly() { + clearInvocations(mMockAudioManager); + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ null, // Selected device doesn't change. + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE); + verifyNoMoreInteractions(mMockAudioManager); + + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP); + verify(mMockAudioManager).removePreferredDeviceForStrategy(mMediaAudioProductStrategy); + } + + @Test + public void getAvailableDevices_ignoresInvalidMediaOutputs() { + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ null, // Selected device doesn't change. + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE); + verifyNoMoreInteractions(mOnDeviceRouteChangedListener); + assertThat( + mControllerUnderTest.getAvailableRoutes().stream() + .map(MediaRoute2Info::getType) + .toList()) + .containsExactly(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); + assertThat(mControllerUnderTest.getSelectedRoute().getType()) + .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); + } + + @Test + public void transferTo_setsTheExpectedRoutingPolicy() { + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP, + FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET); + MediaRoute2Info builtInSpeakerRoute = + getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); + mControllerUnderTest.transferTo(builtInSpeakerRoute.getId()); + verify(mMockAudioManager) + .setPreferredDeviceForStrategy( + mMediaAudioProductStrategy, + createAudioDeviceAttribute(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)); + + MediaRoute2Info wiredHeadsetRoute = + getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET); + mControllerUnderTest.transferTo(wiredHeadsetRoute.getId()); + verify(mMockAudioManager) + .setPreferredDeviceForStrategy( + mMediaAudioProductStrategy, + createAudioDeviceAttribute(AudioDeviceInfo.TYPE_WIRED_HEADSET)); + } + + @Test + public void updateVolume_propagatesCorrectlyToRouteInfo() { + when(mMockAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(2); + when(mMockAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(3); + when(mMockAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)).thenReturn(1); + when(mMockAudioManager.isVolumeFixed()).thenReturn(false); + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET); + + MediaRoute2Info selectedRoute = mControllerUnderTest.getSelectedRoute(); + assertThat(selectedRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADSET); + assertThat(selectedRoute.getVolume()).isEqualTo(2); + assertThat(selectedRoute.getVolumeMax()).isEqualTo(3); + assertThat(selectedRoute.getVolumeHandling()) + .isEqualTo(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE); + + MediaRoute2Info onlyTransferrableRoute = + mControllerUnderTest.getAvailableRoutes().stream() + .filter(it -> !it.equals(selectedRoute)) + .findAny() + .orElseThrow(); + assertThat(onlyTransferrableRoute.getType()) + .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); + assertThat(onlyTransferrableRoute.getVolume()).isEqualTo(0); + assertThat(onlyTransferrableRoute.getVolumeMax()).isEqualTo(0); + assertThat(onlyTransferrableRoute.getVolume()).isEqualTo(0); + assertThat(onlyTransferrableRoute.getVolumeHandling()) + .isEqualTo(MediaRoute2Info.PLAYBACK_VOLUME_FIXED); + + when(mMockAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(0); + when(mMockAudioManager.isVolumeFixed()).thenReturn(true); + mControllerUnderTest.updateVolume(0); + MediaRoute2Info newSelectedRoute = mControllerUnderTest.getSelectedRoute(); + assertThat(newSelectedRoute.getVolume()).isEqualTo(0); + assertThat(newSelectedRoute.getVolumeHandling()) + .isEqualTo(MediaRoute2Info.PLAYBACK_VOLUME_FIXED); + } + + @Test + public void getAvailableRoutes_whenNoProductNameIsProvided_usesTypeToPopulateName() { + assertThat(mControllerUnderTest.getSelectedRoute().getName().toString()) + .isEqualTo(FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER.getProductName().toString()); + + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_NO_NAME, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_NO_NAME); + + MediaRoute2Info selectedRoute = mControllerUnderTest.getSelectedRoute(); + assertThat(selectedRoute.getName().toString()).isEqualTo(FAKE_ROUTE_NAME); + } + + // Internal methods. + + @NonNull + private MediaRoute2Info getAvailableRouteWithType(int type) { + return mControllerUnderTest.getAvailableRoutes().stream() + .filter(it -> it.getType() == type) + .findFirst() + .orElseThrow(); + } + + private void addAvailableAudioDeviceInfo( + @Nullable AudioDeviceInfo newSelectedDevice, AudioDeviceInfo... newAvailableDevices) { + Set<AudioDeviceInfo> newAvailableDeviceInfos = new HashSet<>(mAvailableAudioDeviceInfos); + newAvailableDeviceInfos.addAll(List.of(newAvailableDevices)); + mAvailableAudioDeviceInfos = newAvailableDeviceInfos; + if (newSelectedDevice != null) { + mSelectedAudioDeviceInfo = newSelectedDevice; + } + updateMockAudioManagerState(); + mAudioDeviceCallback.onAudioDevicesAdded(newAvailableDevices); + } + + private void removeAvailableAudioDeviceInfos( + @Nullable AudioDeviceInfo newSelectedDevice, AudioDeviceInfo... devicesToRemove) { + Set<AudioDeviceInfo> newAvailableDeviceInfos = new HashSet<>(mAvailableAudioDeviceInfos); + List.of(devicesToRemove).forEach(newAvailableDeviceInfos::remove); + mAvailableAudioDeviceInfos = newAvailableDeviceInfos; + if (newSelectedDevice != null) { + mSelectedAudioDeviceInfo = newSelectedDevice; + } + updateMockAudioManagerState(); + mAudioDeviceCallback.onAudioDevicesRemoved(devicesToRemove); + } + + private void updateMockAudioManagerState() { + when(mMockAudioManager.getDevicesForAttributes(ATTRIBUTES_MEDIA)) + .thenReturn( + List.of(createAudioDeviceAttribute(mSelectedAudioDeviceInfo.getType()))); + when(mMockAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) + .thenReturn(mAvailableAudioDeviceInfos.toArray(new AudioDeviceInfo[0])); + } + + private static AudioDeviceAttributes createAudioDeviceAttribute(int type) { + // Address is unused. + return new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, type, /* address= */ ""); + } + + private static AudioDeviceInfo createAudioDeviceInfo( + int type, @NonNull String name, @NonNull String address) { + return new AudioDeviceInfo(AudioDevicePort.createForTesting(type, name, address)); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index 3b83e3cc0817..fc2e5b0981e4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -40,6 +40,7 @@ import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; @@ -109,6 +110,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Supplier; @RunWith(AndroidJUnit4.class) @@ -126,6 +128,8 @@ public class GameManagerServiceTests { private MockitoSession mMockingSession; private String mPackageName; + private Map<String, Integer> mPackageCategories; + private Map<String, Integer> mPackageUids; private TestLooper mTestLooper; @Mock private PackageManager mMockPackageManager; @@ -229,34 +233,50 @@ public class GameManagerServiceTests { .strictness(Strictness.WARN) .startMocking(); mMockContext = new MockContext(InstrumentationRegistry.getContext()); + mPackageCategories = new HashMap<>(); + mPackageUids = new HashMap<>(); mPackageName = mMockContext.getPackageName(); - mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME); - final Resources resources = - InstrumentationRegistry.getInstrumentation().getContext().getResources(); - when(mMockPackageManager.getResourcesForApplication(anyString())) - .thenReturn(resources); - when(mMockPackageManager.getPackageUidAsUser(mPackageName, USER_ID_1)).thenReturn( - DEFAULT_PACKAGE_UID); + mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME); LocalServices.addService(PowerManagerInternal.class, mMockPowerManager); mSetFlagsRule.enableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE); + mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP); } - private void mockAppCategory(String packageName, @ApplicationInfo.Category int category) + private void mockAppCategory(String packageName, int packageUid, + @ApplicationInfo.Category int category) throws Exception { reset(mMockPackageManager); - final ApplicationInfo gameApplicationInfo = new ApplicationInfo(); - gameApplicationInfo.category = category; - gameApplicationInfo.packageName = packageName; - final PackageInfo pi = new PackageInfo(); - pi.packageName = packageName; - pi.applicationInfo = gameApplicationInfo; - final List<PackageInfo> packages = new ArrayList<>(); - packages.add(pi); + mPackageCategories.put(packageName, category); + mPackageUids.put(packageName, packageUid); + final List<PackageInfo> packageInfos = new ArrayList<>(); + for (Map.Entry<String, Integer> entry : mPackageCategories.entrySet()) { + + packageName = entry.getKey(); + packageUid = mPackageUids.get(packageName); + category = entry.getValue(); + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = packageName; + applicationInfo.category = category; + when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(), anyInt())) + .thenReturn(applicationInfo); + + final PackageInfo pi = new PackageInfo(); + pi.packageName = packageName; + pi.applicationInfo = applicationInfo; + packageInfos.add(pi); + + when(mMockPackageManager.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn( + packageUid); + when(mMockPackageManager.getPackagesForUid(packageUid)).thenReturn( + new String[]{packageName}); + } when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt())) - .thenReturn(packages); - when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) - .thenReturn(gameApplicationInfo); + .thenReturn(packageInfos); + final Resources resources = + InstrumentationRegistry.getInstrumentation().getContext().getResources(); + when(mMockPackageManager.getResourcesForApplication(anyString())) + .thenReturn(resources); } @After @@ -508,7 +528,7 @@ public class GameManagerServiceTests { @Test public void testGetGameMode_nonGame() throws Exception { - mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO); + mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO); GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); mockModifyGameModeGranted(); assertEquals(GameManager.GAME_MODE_UNSUPPORTED, @@ -780,7 +800,7 @@ public class GameManagerServiceTests { @Test public void testDeviceConfig_nonGame() throws Exception { - mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO); + mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO); mockDeviceConfigAll(); mockModifyGameModeGranted(); checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1)); @@ -1588,7 +1608,7 @@ public class GameManagerServiceTests { @Test public void testSetGameState_nonGame() throws Exception { - mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO); + mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO); mockDeviceConfigNone(); mockModifyGameModeGranted(); GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); @@ -1611,14 +1631,14 @@ public class GameManagerServiceTests { gameManagerService.addGameStateListener(mockListener); verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt()); - mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO); + mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO); GameState gameState = new GameState(true, GameState.MODE_NONE); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); mTestLooper.dispatchAll(); verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt()); - mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME); + mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME); gameState = new GameState(true, GameState.MODE_NONE); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); @@ -1654,7 +1674,7 @@ public class GameManagerServiceTests { gameManagerService.addGameStateListener(mockListener); gameManagerService.removeGameStateListener(mockListener); - mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME); + mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME); GameState gameState = new GameState(false, GameState.MODE_CONTENT); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE)); @@ -1670,13 +1690,17 @@ public class GameManagerServiceTests { return output; } - private void mockInterventionListForMultipleUsers() { + private void mockInterventionListForMultipleUsers() throws Exception { final String[] packageNames = new String[]{"com.android.app0", "com.android.app1", "com.android.app2"}; + int i = 1; + for (String p : packageNames) { + mockAppCategory(p, DEFAULT_PACKAGE_UID + i++, ApplicationInfo.CATEGORY_GAME); + } final ApplicationInfo[] applicationInfos = new ApplicationInfo[3]; final PackageInfo[] pis = new PackageInfo[3]; - for (int i = 0; i < 3; ++i) { + for (i = 0; i < 3; ++i) { applicationInfos[i] = new ApplicationInfo(); applicationInfos[i].category = ApplicationInfo.CATEGORY_GAME; applicationInfos[i].packageName = packageNames[i]; @@ -1717,7 +1741,6 @@ public class GameManagerServiceTests { new Injector()); startUser(gameManagerService, USER_ID_1); startUser(gameManagerService, USER_ID_2); - gameManagerService.setGameModeConfigOverride("com.android.app0", USER_ID_2, GameManager.GAME_MODE_PERFORMANCE, "120", "0.6"); gameManagerService.setGameModeConfigOverride("com.android.app2", USER_ID_2, @@ -1953,7 +1976,7 @@ public class GameManagerServiceTests { @Test public void testUpdateCustomGameModeConfiguration_nonGame() throws Exception { - mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE); + mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_IMAGE); mockModifyGameModeGranted(); GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); gameManagerService.updateCustomGameModeConfiguration(mPackageName, @@ -2013,13 +2036,14 @@ public class GameManagerServiceTests { } @Test - public void testWritingSettingFile_onShutdown() throws InterruptedException { + public void testWritingSettingFile_onShutdown() throws Exception { mockModifyGameModeGranted(); mockDeviceConfigAll(); GameManagerService gameManagerService = new GameManagerService(mMockContext); gameManagerService.onBootCompleted(); startUser(gameManagerService, USER_ID_1); Thread.sleep(500); + mockAppCategory("com.android.app1", DEFAULT_PACKAGE_UID + 1, ApplicationInfo.CATEGORY_GAME); gameManagerService.setGameModeConfigOverride("com.android.app1", USER_ID_1, GameManager.GAME_MODE_BATTERY, "60", "0.5"); gameManagerService.setGameMode("com.android.app1", USER_ID_1, @@ -2259,7 +2283,7 @@ public class GameManagerServiceTests { when(DeviceConfig.getProperty(anyString(), anyString())) .thenReturn(configString); mockModifyGameModeGranted(); - mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE); + mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_IMAGE); GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); assertEquals(GameManager.GAME_MODE_UNSUPPORTED, @@ -2277,9 +2301,7 @@ public class GameManagerServiceTests { mockModifyGameModeGranted(); GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); String someGamePkg = "some.game"; - mockAppCategory(someGamePkg, ApplicationInfo.CATEGORY_GAME); - when(mMockPackageManager.getPackageUidAsUser(someGamePkg, USER_ID_1)).thenReturn( - DEFAULT_PACKAGE_UID + 1); + mockAppCategory(someGamePkg, DEFAULT_PACKAGE_UID + 1, ApplicationInfo.CATEGORY_GAME); gameManagerService.setGameMode(someGamePkg, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameManagerService.getGameMode(someGamePkg, USER_ID_1)); @@ -2307,24 +2329,11 @@ public class GameManagerServiceTests { } @Test - public void testGamePowerMode_gamePackage() throws Exception { - GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); - String[] packages = {mPackageName}; - when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); - gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); - verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); - } - - @Test public void testGamePowerMode_twoGames() throws Exception { GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); - String[] packages1 = {mPackageName}; - when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1); String someGamePkg = "some.game"; - String[] packages2 = {someGamePkg}; int somePackageId = DEFAULT_PACKAGE_UID + 1; - when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2); + mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME); HashMap<Integer, Boolean> powerState = new HashMap<>(); doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1))) .when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean()); @@ -2333,6 +2342,7 @@ public class GameManagerServiceTests { assertTrue(powerState.get(Mode.GAME)); gameManagerService.mUidObserver.onUidStateChanged( DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + assertFalse(powerState.get(Mode.GAME)); gameManagerService.mUidObserver.onUidStateChanged( somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0); assertTrue(powerState.get(Mode.GAME)); @@ -2344,12 +2354,9 @@ public class GameManagerServiceTests { @Test public void testGamePowerMode_twoGamesOverlap() throws Exception { GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); - String[] packages1 = {mPackageName}; - when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1); String someGamePkg = "some.game"; - String[] packages2 = {someGamePkg}; int somePackageId = DEFAULT_PACKAGE_UID + 1; - when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2); + mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME); gameManagerService.mUidObserver.onUidStateChanged( DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( @@ -2363,49 +2370,162 @@ public class GameManagerServiceTests { } @Test - public void testGamePowerMode_released() throws Exception { + public void testGamePowerMode_noPackage() throws Exception { GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); - String[] packages = {mPackageName}; + String[] packages = {}; when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); gameManagerService.mUidObserver.onUidStateChanged( DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); - gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); - verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true); } @Test - public void testGamePowerMode_noPackage() throws Exception { + public void testGamePowerMode_gameAndNotGameApps_flagOn() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP); GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); - String[] packages = {}; - when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); + + String nonGamePkg1 = "not.game1"; + int nonGameUid1 = DEFAULT_PACKAGE_UID + 1; + mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE); + + String nonGamePkg2 = "not.game2"; + int nonGameUid2 = DEFAULT_PACKAGE_UID + 2; + mockAppCategory(nonGamePkg2, nonGameUid2, ApplicationInfo.CATEGORY_IMAGE); + + String gamePkg1 = "game1"; + int gameUid1 = DEFAULT_PACKAGE_UID + 3; + mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME); + + String gamePkg2 = "game2"; + int gameUid2 = DEFAULT_PACKAGE_UID + 4; + mockAppCategory(gamePkg2, gameUid2, ApplicationInfo.CATEGORY_GAME); + + // non-game1 top and background with no-op gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); - verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true); - } + nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0); + gameManagerService.mUidObserver.onUidStateChanged( + nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); - @Test - public void testGamePowerMode_notAGamePackage() throws Exception { - mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE); - GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); - String[] packages = {"someapp"}; - when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); + // game1 top to enable game mode gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); - verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true); + gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); + + // non-game1 in foreground to disable game mode + gameManagerService.mUidObserver.onUidStateChanged( + nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); + + // non-game2 in foreground with no-op + gameManagerService.mUidObserver.onUidStateChanged( + nonGameUid2, ActivityManager.PROCESS_STATE_TOP, 0, 0); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); + + // game 2 in foreground with no-op + gameManagerService.mUidObserver.onUidStateChanged( + gameUid2, ActivityManager.PROCESS_STATE_TOP, 0, 0); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); + + // non-game 1 in background with no-op + gameManagerService.mUidObserver.onUidStateChanged( + nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); + + // non-game2 in background to resume game mode + gameManagerService.mUidObserver.onUidStateChanged( + nonGameUid2, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); + + // game 1 in background with no-op + gameManagerService.mUidObserver.onUidStateChanged( + gameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); + + // game 2 in background to stop game mode + gameManagerService.mUidObserver.onUidStateChanged( + gameUid2, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); } @Test - public void testGamePowerMode_notAGamePackageNotReleased() throws Exception { - mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE); + public void testGamePowerMode_gameAndNotGameApps_flagOff() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP); GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); - String[] packages = {"someapp"}; - when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); + + String nonGamePkg1 = "not.game1"; + int nonGameUid1 = DEFAULT_PACKAGE_UID + 1; + mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE); + + String gamePkg1 = "game1"; + int gameUid1 = DEFAULT_PACKAGE_UID + 3; + mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME); + + // non-game1 top and background with no-op gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); - verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, false); + nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); + + // game1 top to enable game mode + gameManagerService.mUidObserver.onUidStateChanged( + gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); + + // non-game1 in foreground to not interfere + gameManagerService.mUidObserver.onUidStateChanged( + nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); + + // non-game 1 in background to not interfere + gameManagerService.mUidObserver.onUidStateChanged( + nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); + + // move non-game1 to foreground again + gameManagerService.mUidObserver.onUidStateChanged( + nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0); + + // with non-game1 on top, game 1 in background to still disable game mode + gameManagerService.mUidObserver.onUidStateChanged( + gameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); + + // with non-game1 on top, game 1 in foreground to still enable game mode + gameManagerService.mUidObserver.onUidStateChanged( + gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false); + clearInvocations(mMockPowerManager); } @Test @@ -2432,8 +2552,6 @@ public class GameManagerServiceTests { gameManagerService.onBootCompleted(); // Set up a game in the foreground. - String[] packages = {mPackageName}; - when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); gameManagerService.mUidObserver.onUidStateChanged( DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); @@ -2448,10 +2566,8 @@ public class GameManagerServiceTests { // Adding another game to the foreground. String anotherGamePkg = "another.game"; - String[] packages2 = {anotherGamePkg}; - mockAppCategory(anotherGamePkg, ApplicationInfo.CATEGORY_GAME); int somePackageId = DEFAULT_PACKAGE_UID + 1; - when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2); + mockAppCategory(anotherGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME); gameManagerService.mUidObserver.onUidStateChanged( somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0); @@ -2484,8 +2600,6 @@ public class GameManagerServiceTests { })); // Set up a game in the foreground. - String[] packages = {mPackageName}; - when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); gameManagerService.mUidObserver.onUidStateChanged( DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); @@ -2503,10 +2617,8 @@ public class GameManagerServiceTests { // Toggle game default frame rate off. String anotherGamePkg = "another.game"; - String[] packages2 = {anotherGamePkg}; - mockAppCategory(anotherGamePkg, ApplicationInfo.CATEGORY_GAME); int somePackageId = DEFAULT_PACKAGE_UID + 1; - when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2); + mockAppCategory(anotherGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME); gameManagerService.mUidObserver.onUidStateChanged( somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java index c2b52b4ee9c8..57326b2e1e82 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java @@ -50,9 +50,10 @@ import android.util.ArraySet; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.PackageParser2; +import com.android.internal.pm.parsing.PackageParserException; import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Before; @@ -175,7 +176,7 @@ public class ApexManagerTest { mPmService.getPlatformPackage(), /* isUpdatedSystemApp */ false); // isUpdatedSystemApp is ignoreable above, only used for shared library adjustment return parsedPackage.hideAsFinal(); - } catch (PackageManagerException e) { + } catch (PackageParserException e) { throw new RuntimeException(e); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 7b29e2a4159d..538c0ee52424 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -56,6 +56,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.spy import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder import com.android.internal.R +import com.android.internal.pm.parsing.PackageParser2 import com.android.internal.pm.parsing.pkg.PackageImpl import com.android.internal.pm.parsing.pkg.ParsedPackage import com.android.internal.pm.pkg.parsing.ParsingPackage @@ -69,7 +70,6 @@ import com.android.server.compat.PlatformCompat import com.android.server.extendedtestutils.wheneverStatic import com.android.server.pm.dex.DexManager import com.android.server.pm.dex.DynamicCodeLogger -import com.android.server.pm.parsing.PackageParser2 import com.android.server.pm.permission.PermissionManagerServiceInternal import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.resolution.ComponentResolver diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt index da929af3267b..7feafef4e489 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt @@ -21,6 +21,7 @@ import android.content.pm.PackageManager import android.os.Build import android.os.Process import android.util.Log +import com.android.internal.pm.parsing.PackageParserException import com.android.server.pm.pkg.AndroidPackage import com.android.server.testutils.whenever import java.io.File @@ -120,7 +121,7 @@ class PackageManagerServiceBootTest { argThat { path: File -> path.path.contains("a.data.package") }, anyInt(), anyBoolean())) - .thenThrow(PackageManagerException( + .thenThrow(PackageParserException( PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!")) val pm = createPackageManagerService() verify(rule.mocks().settings, Mockito.never()).insertPackageSettingLPw( diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java index 6fca56134393..69817ad5035a 100644 --- a/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java @@ -16,17 +16,21 @@ package com.android.server.audio; -import static android.media.AudioAttributes.USAGE_MEDIA; -import static android.media.AudioAttributes.USAGE_GAME; -import static android.media.AudioAttributes.USAGE_ASSISTANT; import static android.media.AudioAttributes.CONTENT_TYPE_SPEECH; +import static android.media.AudioAttributes.USAGE_ASSISTANT; +import static android.media.AudioAttributes.USAGE_EMERGENCY; +import static android.media.AudioAttributes.USAGE_GAME; +import static android.media.AudioAttributes.USAGE_MEDIA; import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO; import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL; import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK; import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN; +import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION; import android.media.AudioAttributes; +import android.media.FadeManagerConfiguration; import android.media.VolumeShaper; +import android.platform.test.flag.junit.SetFlagsRule; import com.google.common.truth.Expect; @@ -42,6 +46,7 @@ import java.util.List; public final class FadeConfigurationsTest { private FadeConfigurations mFadeConfigurations; private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000; + private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000; private static final long DEFAULT_DELAY_FADE_IN_OFFENDERS_MS = 2000; private static final long DURATION_FOR_UNFADEABLE_MS = 0; private static final int TEST_UID_SYSTEM = 1000; @@ -60,11 +65,19 @@ public final class FadeConfigurationsTest { private static final VolumeShaper.Configuration DEFAULT_FADEOUT_VSHAPE = new VolumeShaper.Configuration.Builder() .setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID) - .setCurve(/* times= */new float[]{0.f, 0.25f, 1.0f} , - /* volumes= */new float[]{1.f, 0.65f, 0.0f}) + .setCurve(/* times= */ new float[]{0.f, 0.25f, 1.0f}, + /* volumes= */ new float[]{1.f, 0.65f, 0.0f}) .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME) .setDuration(DEFAULT_FADE_OUT_DURATION_MS) .build(); + private static final VolumeShaper.Configuration DEFAULT_FADEIN_VSHAPE = + new VolumeShaper.Configuration.Builder() + .setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID) + .setCurve(/* times= */ new float[]{0.f, 0.50f, 1.0f}, + /* volumes= */ new float[]{0.f, 0.30f, 1.0f}) + .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME) + .setDuration(DEFAULT_FADE_IN_DURATION_MS) + .build(); private static final AudioAttributes TEST_MEDIA_AUDIO_ATTRIBUTE = new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build(); @@ -72,12 +85,18 @@ public final class FadeConfigurationsTest { new AudioAttributes.Builder().setUsage(USAGE_GAME).build(); private static final AudioAttributes TEST_ASSISTANT_AUDIO_ATTRIBUTE = new AudioAttributes.Builder().setUsage(USAGE_ASSISTANT).build(); + private static final AudioAttributes TEST_EMERGENCY_AUDIO_ATTRIBUTE = + new AudioAttributes.Builder().setSystemUsage(USAGE_EMERGENCY).build(); + private static final AudioAttributes TEST_SPEECH_AUDIO_ATTRIBUTE = new AudioAttributes.Builder().setContentType(CONTENT_TYPE_SPEECH).build(); @Rule public final Expect expect = Expect.create(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Before public void setUp() { mFadeConfigurations = new FadeConfigurations(); @@ -156,4 +175,110 @@ public final class FadeConfigurationsTest { .that(mFadeConfigurations.isFadeable(TEST_GAME_AUDIO_ATTRIBUTE, TEST_UID_USER, PLAYER_TYPE_AAUDIO)).isFalse(); } + + @Test + public void testGetFadeableUsages_withFadeManagerConfigurations_equals() { + mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION); + List<Integer> usageList = List.of(AudioAttributes.USAGE_ALARM, + AudioAttributes.USAGE_EMERGENCY); + FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ usageList, + /* unfadeableContentTypes= */ null, /* unfadeableUids= */ null, + /* unfadeableAudioAttrs= */ null); + FadeConfigurations fadeConfigs = new FadeConfigurations(); + + fadeConfigs.setFadeManagerConfiguration(fmc); + + expect.withMessage("Fadeable usages with fade manager configuration") + .that(fadeConfigs.getFadeableUsages()).isEqualTo(fmc.getFadeableUsages()); + } + + @Test + public void testGetUnfadeableContentTypes_withFadeManagerConfigurations_equals() { + mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION); + List<Integer> contentTypesList = List.of(AudioAttributes.CONTENT_TYPE_MUSIC, + AudioAttributes.CONTENT_TYPE_MOVIE); + FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null, + /* unfadeableContentTypes= */ contentTypesList, /* unfadeableUids= */ null, + /* unfadeableAudioAttrs= */ null); + FadeConfigurations fadeConfigs = new FadeConfigurations(); + + fadeConfigs.setFadeManagerConfiguration(fmc); + + expect.withMessage("Unfadeable content types with fade manager configuration") + .that(fadeConfigs.getUnfadeableContentTypes()) + .isEqualTo(fmc.getUnfadeableContentTypes()); + } + + @Test + public void testGetUnfadeableAudioAttributes_withFadeManagerConfigurations_equals() { + mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION); + List<AudioAttributes> attrsList = List.of(TEST_ASSISTANT_AUDIO_ATTRIBUTE, + TEST_EMERGENCY_AUDIO_ATTRIBUTE); + FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null, + /* unfadeableContentTypes= */ null, /* unfadeableUids= */ null, + /* unfadeableAudioAttrs= */ attrsList); + FadeConfigurations fadeConfigs = new FadeConfigurations(); + + fadeConfigs.setFadeManagerConfiguration(fmc); + + expect.withMessage("Unfadeable audio attributes with fade manager configuration") + .that(fadeConfigs.getUnfadeableAudioAttributes()) + .isEqualTo(fmc.getUnfadeableAudioAttributes()); + } + + @Test + public void testGetUnfadeableUids_withFadeManagerConfigurations_equals() { + mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION); + List<Integer> uidsList = List.of(TEST_UID_SYSTEM, TEST_UID_USER); + FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null, + /* unfadeableContentTypes= */ null, /* unfadeableUids= */ uidsList, + /* unfadeableAudioAttrs= */ null); + FadeConfigurations fadeConfigs = new FadeConfigurations(); + + fadeConfigs.setFadeManagerConfiguration(fmc); + + expect.withMessage("Unfadeable uids with fade manager configuration") + .that(fadeConfigs.getUnfadeableUids()).isEqualTo(fmc.getUnfadeableUids()); + } + + private static FadeManagerConfiguration createFadeMgrConfig(List<Integer> fadeableUsages, + List<Integer> unfadeableContentTypes, List<Integer> unfadeableUids, + List<AudioAttributes> unfadeableAudioAttrs) { + FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder(); + if (fadeableUsages != null) { + builder.setFadeableUsages(fadeableUsages); + } + if (unfadeableContentTypes != null) { + builder.setUnfadeableContentTypes(unfadeableContentTypes); + } + if (unfadeableUids != null) { + builder.setUnfadeableUids(unfadeableUids); + } + if (unfadeableAudioAttrs != null) { + builder.setUnfadeableAudioAttributes(unfadeableAudioAttrs); + } + if (fadeableUsages != null) { + for (int index = 0; index < fadeableUsages.size(); index++) { + builder.setFadeOutVolumeShaperConfigForAudioAttributes( + createGenericAudioAttributesForUsage(fadeableUsages.get(index)), + DEFAULT_FADEOUT_VSHAPE); + } + } + if (fadeableUsages != null) { + for (int index = 0; index < fadeableUsages.size(); index++) { + builder.setFadeInVolumeShaperConfigForAudioAttributes( + createGenericAudioAttributesForUsage(fadeableUsages.get(index)), + DEFAULT_FADEIN_VSHAPE); + } + } + + return builder.build(); + } + + private static AudioAttributes createGenericAudioAttributesForUsage(int usage) { + if (AudioAttributes.isSystemUsage(usage)) { + return new AudioAttributes.Builder().setSystemUsage(usage).build(); + } + return new AudioAttributes.Builder().setUsage(usage).build(); + } } diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java index 65059d5ca8fd..fa94821d4ff2 100644 --- a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java @@ -23,8 +23,6 @@ import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO; import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK; import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN; -import static org.junit.Assert.assertThrows; - import android.content.Context; import android.media.AudioAttributes; import android.media.AudioManager; @@ -75,20 +73,11 @@ public final class FadeOutManagerTest { @Before public void setUp() { - mFadeOutManager = new FadeOutManager(new FadeConfigurations()); + mFadeOutManager = new FadeOutManager(); mContext = ApplicationProvider.getApplicationContext(); } @Test - public void constructor_nullFadeConfigurations_fails() { - Throwable thrown = assertThrows(NullPointerException.class, () -> new FadeOutManager( - /* FadeConfigurations= */ null)); - - expect.withMessage("Constructor exception") - .that(thrown).hasMessageThat().contains("Fade configurations can not be null"); - } - - @Test public void testCanCauseFadeOut_forFaders_returnsTrue() { FocusRequester winner = createFocusRequester(TEST_MEDIA_AUDIO_ATTRIBUTE, "winning-client", "unit-test", TEST_UID_USER, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java index 94cb860ae710..74f8f086803f 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java @@ -29,7 +29,7 @@ import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.platform.test.annotations.Presubmit; -import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.filters.SmallTest; @@ -43,10 +43,10 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; -@RequiresFlagsDisabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR) @Presubmit @SmallTest public class SensorOverlaysTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private static final int SENSOR_ID = 11; private static final long REQUEST_ID = 8; @@ -59,6 +59,7 @@ public class SensorOverlaysTest { @Before public void setup() { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); when(mAcquisitionClient.getRequestId()).thenReturn(REQUEST_ID); when(mAcquisitionClient.hasRequestId()).thenReturn(true); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index c24227fcd1f7..774ea5bc6b16 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -161,6 +161,7 @@ public class FingerprintAuthenticationClientTest { @Before public void setup() { + mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR); mContext.addMockSystemService(BiometricManager.class, mBiometricManager); when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); when(mBiometricLogger.getAmbientLightProbe(anyBoolean())).thenAnswer(i -> diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java index edfe1b416f22..071d571adabe 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright 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. @@ -24,10 +24,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; -import android.annotation.Nullable; import android.companion.virtual.camera.VirtualCameraCallback; import android.companion.virtual.camera.VirtualCameraConfig; -import android.companion.virtual.camera.VirtualCameraMetadata; import android.companion.virtual.camera.VirtualCameraStreamConfig; import android.companion.virtualcamera.IVirtualCameraService; import android.companion.virtualcamera.VirtualCameraConfiguration; @@ -156,10 +154,6 @@ public class VirtualCameraControllerTest { @NonNull VirtualCameraStreamConfig streamConfig) {} @Override - public void onProcessCaptureRequest( - int streamId, long frameId, @Nullable VirtualCameraMetadata metadata) {} - - @Override public void onStreamClosed(int streamId) {} }; } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java new file mode 100644 index 000000000000..d9a38eb121ac --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 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.companion.virtual.camera; + +import android.companion.virtual.camera.VirtualCameraStreamConfig; +import android.graphics.ImageFormat; +import android.graphics.PixelFormat; +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.google.common.testing.EqualsTester; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class VirtualCameraStreamConfigTest { + + private static final int VGA_WIDTH = 640; + private static final int VGA_HEIGHT = 480; + + private static final int QVGA_WIDTH = 320; + private static final int QVGA_HEIGHT = 240; + + @Test + public void testEquals() { + VirtualCameraStreamConfig vgaYuvStreamConfig = new VirtualCameraStreamConfig(VGA_WIDTH, + VGA_HEIGHT, + ImageFormat.YUV_420_888); + VirtualCameraStreamConfig qvgaYuvStreamConfig = new VirtualCameraStreamConfig(QVGA_WIDTH, + QVGA_HEIGHT, ImageFormat.YUV_420_888); + VirtualCameraStreamConfig vgaRgbaStreamConfig = new VirtualCameraStreamConfig(VGA_WIDTH, + VGA_HEIGHT, PixelFormat.RGBA_8888); + + new EqualsTester() + .addEqualityGroup(vgaYuvStreamConfig, reparcel(vgaYuvStreamConfig)) + .addEqualityGroup(qvgaYuvStreamConfig, reparcel(qvgaYuvStreamConfig)) + .addEqualityGroup(vgaRgbaStreamConfig, reparcel(vgaRgbaStreamConfig)) + .testEquals(); + } + + private static VirtualCameraStreamConfig reparcel(VirtualCameraStreamConfig config) { + Parcel parcel = Parcel.obtain(); + try { + config.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + return VirtualCameraStreamConfig.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + } + + +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java index a63d01b6225a..e7da26ea38b1 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java @@ -456,6 +456,14 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { } @Test + public void avbEnabled_standby_avbDisabled() { + enableAbsoluteVolumeBehavior(); + mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); + assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( + AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + } + + @Test public void avbEnabled_cecVolumeDisabled_avbDisabled() { enableAbsoluteVolumeBehavior(); diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java index ce15c6d30531..eb9cce007b77 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -67,10 +67,10 @@ import android.provider.Settings; import androidx.test.InstrumentationRegistry; import com.android.internal.R; +import com.android.internal.pm.parsing.PackageParser2; import com.android.server.compat.PlatformCompat; import com.android.server.integrity.engine.RuleEvaluationEngine; import com.android.server.integrity.model.IntegrityCheckResult; -import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.TestPackageParser2; import com.android.server.testutils.TestUtils; diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java index 1bfd43ff60ef..25eedf5652d7 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java @@ -37,6 +37,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.servicestests.R; +import com.android.internal.pm.parsing.PackageParserException; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.PackageManagerException; import com.android.server.pm.parsing.TestPackageParser2; @@ -161,7 +162,8 @@ public class DexMetadataHelperTest { } @Test - public void testParsePackageWithDmFileValid() throws IOException, PackageManagerException { + public void testParsePackageWithDmFileValid() throws IOException, PackageManagerException, + PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk"); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); @@ -178,7 +180,7 @@ public class DexMetadataHelperTest { @Test public void testParsePackageSplitsWithDmFileValid() - throws IOException, PackageManagerException { + throws IOException, PackageManagerException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a); createDexMetadataFile("install_split_base.apk"); @@ -201,7 +203,7 @@ public class DexMetadataHelperTest { @Test public void testParsePackageSplitsNoBaseWithDmFileValid() - throws IOException, PackageManagerException { + throws IOException, PackageManagerException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a); createDexMetadataFile("install_split_feature_a.apk"); @@ -219,7 +221,7 @@ public class DexMetadataHelperTest { } @Test - public void testParsePackageWithDmFileInvalid() throws IOException { + public void testParsePackageWithDmFileInvalid() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); File invalidDmFile = new File(mTmpDir, "install_split_base.dm"); Files.createFile(invalidDmFile.toPath()); @@ -242,7 +244,7 @@ public class DexMetadataHelperTest { @Test public void testParsePackageSplitsWithDmFileInvalid() - throws IOException, PackageManagerException { + throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a); createDexMetadataFile("install_split_base.apk"); @@ -268,7 +270,7 @@ public class DexMetadataHelperTest { @Test public void testParsePackageWithDmFileInvalidManifest() - throws IOException, PackageManagerException { + throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", /*validManifest=*/false); @@ -283,7 +285,7 @@ public class DexMetadataHelperTest { @Test public void testParsePackageWithDmFileEmptyManifest() - throws IOException, PackageManagerException { + throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", /*packageName=*/"doesn't matter", /*versionCode=*/-12345L, /*emptyManifest=*/true, /*validManifest=*/true); @@ -299,7 +301,7 @@ public class DexMetadataHelperTest { @Test public void testParsePackageWithDmFileBadPackageName() - throws IOException, PackageManagerException { + throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", /*packageName=*/"bad package name", DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true); @@ -315,7 +317,7 @@ public class DexMetadataHelperTest { @Test public void testParsePackageWithDmFileBadVersionCode() - throws IOException, PackageManagerException { + throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME, /*versionCode=*/12345L, /*emptyManifest=*/false, /*validManifest=*/true); @@ -331,7 +333,7 @@ public class DexMetadataHelperTest { @Test public void testParsePackageWithDmFileMissingPackageName() - throws IOException, PackageManagerException { + throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", /*packageName=*/null, DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true); @@ -347,7 +349,7 @@ public class DexMetadataHelperTest { @Test public void testParsePackageWithDmFileMissingVersionCode() - throws IOException, PackageManagerException { + throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME, /*versionCode=*/null, /*emptyManifest=*/false, /*validManifest=*/true); diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt index 9b4ca2a86f2c..6a088d99588e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt @@ -36,7 +36,7 @@ import org.xmlpull.v1.XmlPullParserFactory @Postsubmit class AndroidPackageParsingValidationTest { companion object { - private val parser2 = PackageParser2.forParsingFileWithDefaults() + private val parser2 = PackageParserUtils.forParsingFileWithDefaults() private val apks = ((PackageManagerService.SYSTEM_PARTITIONS) .flatMap { listOfNotNull(it.privAppFolder, it.appFolder, it.overlayFolder) diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt index c44f583a93ef..e420e4b52b6e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt @@ -18,11 +18,12 @@ package com.android.server.pm.parsing import android.content.pm.ApplicationInfo import android.util.ArraySet +import com.android.internal.pm.parsing.PackageParser2 import java.io.File class TestPackageParser2(var cacheDir: File? = null) : PackageParser2( null /* separateProcesses */, null /* displayMetrics */, - cacheDir /* cacheDir */, object : PackageParser2.Callback() { + cacheDir?.let { PackageCacher(cacheDir) }, object : PackageParser2.Callback() { override fun isChangeEnabled(changeId: Long, appInfo: ApplicationInfo): Boolean { return true } diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 848790381984..89056cc34795 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -64,17 +64,18 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; import static org.mockito.Matchers.intThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.usage.AppStandbyInfo; import android.app.usage.UsageEvents; +import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.appwidget.AppWidgetManager; import android.content.Context; @@ -99,7 +100,6 @@ import android.util.Pair; import android.view.Display; import androidx.test.InstrumentationRegistry; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -110,6 +110,7 @@ import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -125,6 +126,7 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; import java.util.stream.Collectors; /** @@ -175,6 +177,9 @@ public class AppStandbyControllerTests { private static final int ASSERT_RETRY_ATTEMPTS = 20; private static final int ASSERT_RETRY_DELAY_MILLISECONDS = 500; + @DurationMillisLong + private static final long FLUSH_TIMEOUT_MILLISECONDS = 5_000; + /** Mock variable used in {@link MyInjector#isPackageInstalled(String, int, int)} */ private static boolean isPackageInstalled = true; @@ -563,6 +568,7 @@ public class AppStandbyControllerTests { mInjector = new MyInjector(myContext, Looper.getMainLooper()); mController = setupController(); setupInitialUsageHistory(); + flushHandler(mController); } @After @@ -571,12 +577,9 @@ public class AppStandbyControllerTests { } @Test - @FlakyTest(bugId = 185169504) public void testBoundWidgetPackageExempt() throws Exception { assumeTrue(mInjector.getContext().getSystemService(AppWidgetManager.class) != null); - assertEquals(STANDBY_BUCKET_ACTIVE, - mController.getAppStandbyBucket(PACKAGE_EXEMPTED_1, USER_ID, - mInjector.mElapsedRealtime, false)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_EXEMPTED_1); } @Test @@ -654,7 +657,6 @@ public class AppStandbyControllerTests { } @Test - @FlakyTest(bugId = 185169504) public void testIsAppIdle_Charging() throws Exception { TestParoleListener paroleListener = new TestParoleListener(); mController.addListener(paroleListener); @@ -662,7 +664,7 @@ public class AppStandbyControllerTests { setChargingState(mController, false); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM); - assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); assertFalse(mController.isInParole()); @@ -671,7 +673,7 @@ public class AppStandbyControllerTests { setChargingState(mController, true); paroleListener.awaitOnLatch(2000); assertTrue(paroleListener.getParoleState()); - assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); assertTrue(mController.isInParole()); @@ -680,14 +682,13 @@ public class AppStandbyControllerTests { setChargingState(mController, false); paroleListener.awaitOnLatch(2000); assertFalse(paroleListener.getParoleState()); - assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); assertFalse(mController.isInParole()); } @Test - @FlakyTest(bugId = 185169504) public void testIsAppIdle_Enabled() throws Exception { setChargingState(mController, false); TestParoleListener paroleListener = new TestParoleListener(); @@ -696,7 +697,7 @@ public class AppStandbyControllerTests { setAppIdleEnabled(mController, true); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM); - assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); @@ -711,7 +712,7 @@ public class AppStandbyControllerTests { setAppIdleEnabled(mController, true); paroleListener.awaitOnLatch(2000); assertFalse(paroleListener.getParoleState()); - assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); } @@ -723,6 +724,7 @@ public class AppStandbyControllerTests { private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket, int userId) { mInjector.mElapsedRealtime = elapsedTime; + flushHandler(controller); controller.checkIdleStates(userId); assertEquals(bucket, controller.getAppStandbyBucket(PACKAGE_1, userId, mInjector.mElapsedRealtime, @@ -744,50 +746,85 @@ public class AppStandbyControllerTests { } private int getStandbyBucket(int userId, AppStandbyController controller, String packageName) { + flushHandler(controller); return controller.getAppStandbyBucket(packageName, userId, mInjector.mElapsedRealtime, true); } + private List<AppStandbyInfo> getStandbyBuckets(int userId) { + flushHandler(mController); + return mController.getAppStandbyBuckets(userId); + } + private int getStandbyBucketReason(String packageName) { + flushHandler(mController); return mController.getAppStandbyBucketReason(packageName, USER_ID, mInjector.mElapsedRealtime); } - private void assertBucket(int bucket) throws InterruptedException { - assertBucket(bucket, PACKAGE_1); + private void waitAndAssertBucket(int bucket, String pkg) { + waitAndAssertBucket(mController, bucket, pkg); } - private void assertBucket(int bucket, String pkg) throws InterruptedException { - int retries = 0; - do { - if (bucket == getStandbyBucket(mController, pkg)) { - // Success - return; - } - Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS); - retries++; - } while(retries < ASSERT_RETRY_ATTEMPTS); - // try one last time - assertEquals(bucket, getStandbyBucket(mController, pkg)); + private void waitAndAssertBucket(AppStandbyController controller, int bucket, String pkg) { + StringBuilder sb = new StringBuilder(); + sb.append(pkg); + sb.append(" was not in the "); + sb.append(UsageStatsManager.standbyBucketToString(bucket)); + sb.append(" ("); + sb.append(bucket); + sb.append(") bucket."); + waitAndAssertBucket(sb.toString(), controller, bucket, pkg); + } + + private void waitAndAssertBucket(String msg, int bucket, String pkg) { + waitAndAssertBucket(msg, mController, bucket, pkg); } - private void assertNotBucket(int bucket) throws InterruptedException { - final String pkg = PACKAGE_1; + private void waitAndAssertBucket(String msg, AppStandbyController controller, int bucket, + String pkg) { + waitAndAssertBucket(msg, controller, bucket, USER_ID, pkg); + } + + private void waitAndAssertBucket(String msg, AppStandbyController controller, int bucket, + int userId, + String pkg) { + waitUntil(() -> bucket == getStandbyBucket(userId, controller, pkg)); + assertEquals(msg, bucket, getStandbyBucket(userId, controller, pkg)); + } + + private void waitAndAssertNotBucket(int bucket, String pkg) { + waitAndAssertNotBucket(mController, bucket, pkg); + } + + private void waitAndAssertNotBucket(AppStandbyController controller, int bucket, String pkg) { + waitUntil(() -> bucket != getStandbyBucket(controller, pkg)); + assertNotEquals(bucket, getStandbyBucket(controller, pkg)); + } + + private void waitAndAssertLastNoteEvent(int event) { + waitUntil(() -> { + flushHandler(mController); + return event == mInjector.mLastNoteEvent; + }); + assertEquals(event, mInjector.mLastNoteEvent); + } + + // Waits until condition is true or times out. + private void waitUntil(BooleanSupplier resultSupplier) { int retries = 0; do { - if (bucket != getStandbyBucket(mController, pkg)) { - // Success - return; + if (resultSupplier.getAsBoolean()) return; + try { + Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS); + } catch (InterruptedException ie) { + // Do nothing } - Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS); retries++; - } while(retries < ASSERT_RETRY_ATTEMPTS); - // try one last time - assertNotEquals(bucket, getStandbyBucket(mController, pkg)); + } while (retries < ASSERT_RETRY_ATTEMPTS); } @Test - @FlakyTest(bugId = 185169504) public void testBuckets() throws Exception { assertTimeout(mController, 0, STANDBY_BUCKET_NEVER); @@ -820,14 +857,13 @@ public class AppStandbyControllerTests { } @Test - @FlakyTest(bugId = 185169504) public void testSetAppStandbyBucket() throws Exception { // For a known package, standby bucket should be set properly reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_TIMEOUT); - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // For an unknown package, standby bucket should not be set, hence NEVER is returned // Ensure the unknown package is not already in history by removing it @@ -836,21 +872,20 @@ public class AppStandbyControllerTests { mController.setAppStandbyBucket(PACKAGE_UNKNOWN, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_TIMEOUT); isPackageInstalled = true; // Reset mocked variable for other tests - assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN)); + waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_UNKNOWN); } @Test - @FlakyTest(bugId = 185169504) public void testAppStandbyBucketOnInstallAndUninstall() throws Exception { // On package install, standby bucket should be ACTIVE reportEvent(mController, USER_INTERACTION, 0, PACKAGE_UNKNOWN); - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_UNKNOWN)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_UNKNOWN); // On uninstall, package should not exist in history and should return a NEVER bucket mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID); - assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN)); + waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_UNKNOWN); // Ensure uninstalled app is not in history - List<AppStandbyInfo> buckets = mController.getAppStandbyBuckets(USER_ID); + List<AppStandbyInfo> buckets = getStandbyBuckets(USER_ID); for(AppStandbyInfo bucket : buckets) { if (bucket.mPackageName.equals(PACKAGE_UNKNOWN)) { fail("packageName found in app idle history after uninstall."); @@ -859,7 +894,6 @@ public class AppStandbyControllerTests { } @Test - @FlakyTest(bugId = 185169504) public void testScreenTimeAndBuckets() throws Exception { mInjector.setDisplayOn(false); @@ -876,22 +910,21 @@ public class AppStandbyControllerTests { // RARE bucket, should fail because the screen wasn't ON. mInjector.mElapsedRealtime = RARE_THRESHOLD + 1; mController.checkIdleStates(USER_ID); - assertNotEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertNotBucket(STANDBY_BUCKET_RARE, PACKAGE_1); mInjector.setDisplayOn(true); assertTimeout(mController, RARE_THRESHOLD + 2 * HOUR_MS + 1, STANDBY_BUCKET_RARE); } @Test - @FlakyTest(bugId = 185169504) public void testForcedIdle() throws Exception { mController.forceIdleState(PACKAGE_1, USER_ID, true); - assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); mController.forceIdleState(PACKAGE_1, USER_ID, false); - assertEquals(STANDBY_BUCKET_ACTIVE, mController.getAppStandbyBucket(PACKAGE_1, USER_ID, 0, - true)); + + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); } @@ -901,15 +934,15 @@ public class AppStandbyControllerTests { .onPropertiesChanged(mInjector.getDeviceConfigProperties()); reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime = 1; rearmQuotaBumpLatch(PACKAGE_1, USER_ID); reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mController.forceIdleState(PACKAGE_1, USER_ID, true); reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); - assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); assertFalse(mQuotaBumpLatch.await(1, TimeUnit.SECONDS)); } @@ -917,9 +950,10 @@ public class AppStandbyControllerTests { public void testNotificationEvent_bucketPromotion_changePromotedBucket() throws Exception { mInjector.mPropertiesChangedListener .onPropertiesChanged(mInjector.getDeviceConfigProperties()); + mInjector.mElapsedRealtime += RARE_THRESHOLD + 1; mController.forceIdleState(PACKAGE_1, USER_ID, true); reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); - assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); // TODO: Avoid hardcoding these string constants. mInjector.mSettingsBuilder.setInt("notification_seen_promoted_bucket", @@ -928,11 +962,10 @@ public class AppStandbyControllerTests { mInjector.getDeviceConfigProperties()); mController.forceIdleState(PACKAGE_1, USER_ID, true); reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); - assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testNotificationEvent_quotaBump() throws Exception { mInjector.mSettingsBuilder .setBoolean("trigger_quota_bump_on_notification_seen", true); @@ -942,7 +975,7 @@ public class AppStandbyControllerTests { .onPropertiesChanged(mInjector.getDeviceConfigProperties()); reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime = RARE_THRESHOLD * 2; setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM); @@ -951,83 +984,80 @@ public class AppStandbyControllerTests { reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); assertTrue(mQuotaBumpLatch.await(1, TimeUnit.SECONDS)); - assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testSlicePinnedEvent() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime = 1; reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime, PACKAGE_1); - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mController.forceIdleState(PACKAGE_1, USER_ID, true); reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime, PACKAGE_1); - assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testSlicePinnedPrivEvent() throws Exception { mController.forceIdleState(PACKAGE_1, USER_ID, true); reportEvent(mController, SLICE_PINNED_PRIV, mInjector.mElapsedRealtime, PACKAGE_1); - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testPredictionTimedOut() throws Exception { // Set it to timeout or usage, so that prediction can override it mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_TIMEOUT); - assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_PREDICTED); - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Fast forward 12 hours mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD; mController.checkIdleStates(USER_ID); // Should still be in predicted bucket, since prediction timeout is 1 day since prediction - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Fast forward two more hours mInjector.mElapsedRealtime += 2 * HOUR_MS; mController.checkIdleStates(USER_ID); // Should have now applied prediction timeout - assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); // Fast forward RARE bucket mInjector.mElapsedRealtime += RARE_THRESHOLD; mController.checkIdleStates(USER_ID); // Should continue to apply prediction timeout - assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) + @Ignore("b/317086276") public void testOverrides() throws Exception { // Can force to NEVER mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, REASON_MAIN_FORCED_BY_USER); - assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_1); // Prediction can't override FORCED reasons mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_PREDICTED); - assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_MAIN_FORCED_BY_USER); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_PREDICTED); - assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1); // Prediction can't override NEVER mInjector.mElapsedRealtime = 2 * HOUR_MS; @@ -1035,115 +1065,114 @@ public class AppStandbyControllerTests { REASON_MAIN_DEFAULT); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_PREDICTED); - assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_1); // Prediction can't set to NEVER mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_USAGE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, REASON_MAIN_PREDICTED); - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Prediction can't remove from RESTRICTED mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_USER); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_PREDICTED); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_SYSTEM); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_PREDICTED); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Force from user can remove from RESTRICTED mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_USER); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_FORCED_BY_USER); - assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_SYSTEM); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_FORCED_BY_USER); - assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); // Force from system can remove from RESTRICTED if it was put it in due to system mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_SYSTEM); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_FORCED_BY_SYSTEM); - assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_USER); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_FORCED_BY_SYSTEM); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_PREDICTED); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_FORCED_BY_SYSTEM); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Non-user usage can't remove from RESTRICTED mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_SYSTEM); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_USAGE); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_USAGE | REASON_SUB_USAGE_SYSTEM_INTERACTION); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_USAGE | REASON_SUB_USAGE_SYNC_ADAPTER); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_USAGE | REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_NON_DOZE); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Explicit user usage can remove from RESTRICTED mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_USER); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION); - assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_SYSTEM); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND); - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testTimeout() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime = 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // bucketing works after timeout mInjector.mElapsedRealtime = mController.mPredictionTimeoutMillis - 100; mController.checkIdleStates(USER_ID); // Use recent prediction - assertBucket(STANDBY_BUCKET_FREQUENT); + waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1); + waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1); // Way past prediction timeout, use system thresholds mInjector.mElapsedRealtime = RARE_THRESHOLD; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RARE); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); } /** Test that timeouts still work properly even if invalid configuration values are set. */ @Test - @FlakyTest(bugId = 185169504) public void testTimeout_InvalidThresholds() throws Exception { mInjector.mSettingsBuilder .setLong("screen_threshold_active", -1) @@ -1161,19 +1190,19 @@ public class AppStandbyControllerTests { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime = HOUR_MS; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_FREQUENT); + waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1); mInjector.mElapsedRealtime = 2 * HOUR_MS; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RARE); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); mInjector.mElapsedRealtime = 4 * HOUR_MS; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); } /** @@ -1181,74 +1210,72 @@ public class AppStandbyControllerTests { * timeout has passed. */ @Test - @FlakyTest(bugId = 185169504) + @Ignore("b/317086276") public void testTimeoutBeforeRestricted() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_SYSTEM); // Bucket shouldn't change - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // bucketing works after timeout mInjector.mElapsedRealtime += DAY_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_SYSTEM); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Way past all timeouts. Make sure timeout processing doesn't raise bucket. mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); } /** * Test that an app is put into the RESTRICTED bucket after enough time has passed. */ @Test - @FlakyTest(bugId = 185169504) public void testRestrictedDelay() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime += mInjector.getAutoRestrictedBucketDelayMs() - 5000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_SYSTEM); // Bucket shouldn't change - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // bucketing works after timeout mInjector.mElapsedRealtime += 6000; Thread.sleep(6000); // Enough time has passed. The app should automatically be put into the RESTRICTED bucket. - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); } /** * Test that an app is put into the RESTRICTED bucket after enough time has passed. */ @Test - @FlakyTest(bugId = 185169504) public void testRestrictedDelay_DelayChange() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mAutoRestrictedBucketDelayMs = 2 * HOUR_MS; mInjector.mElapsedRealtime += 2 * HOUR_MS - 5000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_SYSTEM); // Bucket shouldn't change - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // bucketing works after timeout mInjector.mElapsedRealtime += 6000; Thread.sleep(6000); // Enough time has passed. The app should automatically be put into the RESTRICTED bucket. - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); } /** @@ -1256,36 +1283,35 @@ public class AppStandbyControllerTests { * a low bucket after the RESTRICTED timeout. */ @Test - @FlakyTest(bugId = 185169504) public void testRestrictedTimeoutOverridesRestoredLowBucketPrediction() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Predict to RARE Not long enough to time out into RESTRICTED. mInjector.mElapsedRealtime += RARE_THRESHOLD; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_RARE); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); // Add a short timeout event mInjector.mElapsedRealtime += 1000; reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime += 1000; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Long enough that it could have timed out into RESTRICTED. Instead of reverting to // predicted RARE, should go into RESTRICTED mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Ensure that prediction can still raise it out despite this override. mInjector.mElapsedRealtime += 1; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); } /** @@ -1293,7 +1319,6 @@ public class AppStandbyControllerTests { * a low bucket after the RESTRICTED timeout. */ @Test - @FlakyTest(bugId = 185169504) public void testRestrictedTimeoutOverridesPredictionLowBucket() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); @@ -1301,7 +1326,7 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime += RARE_THRESHOLD; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_RARE); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); mInjector.mElapsedRealtime += 1; reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); @@ -1310,10 +1335,10 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); } /** @@ -1321,261 +1346,250 @@ public class AppStandbyControllerTests { * interaction. */ @Test - @FlakyTest(bugId = 185169504) public void testSystemInteractionOverridesRestrictedTimeout() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Long enough that it could have timed out into RESTRICTED. mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Report system interaction. mInjector.mElapsedRealtime += 1000; reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); // Ensure that it's raised out of RESTRICTED for the system interaction elevation duration. - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime += 1000; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Elevation duration over. Should fall back down. mInjector.mElapsedRealtime += 10 * MINUTE_MS; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testPredictionRaiseFromRestrictedTimeout_highBucket() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); // Way past all timeouts. App times out into RESTRICTED bucket. mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Since the app timed out into RESTRICTED, prediction should be able to remove from the // bucket. mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testPredictionRaiseFromRestrictedTimeout_lowBucket() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); // Way past all timeouts. App times out into RESTRICTED bucket. mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Prediction into a low bucket means no expectation of the app being used, so we shouldn't // elevate the app from RESTRICTED. mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testCascadingTimeouts() throws Exception { mInjector.mPropertiesChangedListener .onPropertiesChanged(mInjector.getDeviceConfigProperties()); reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); reportEvent(mController, NOTIFICATION_SEEN, 1000, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime = 2000 + mController.mStrongUsageTimeoutMillis; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_WORKING_SET); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); mInjector.mElapsedRealtime = 2000 + mController.mNotificationSeenTimeoutMillis; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_FREQUENT); + waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testOverlappingTimeouts() throws Exception { mInjector.mPropertiesChangedListener .onPropertiesChanged(mInjector.getDeviceConfigProperties()); reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); reportEvent(mController, NOTIFICATION_SEEN, 1000, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Overlapping USER_INTERACTION before previous one times out reportEvent(mController, USER_INTERACTION, mController.mStrongUsageTimeoutMillis - 1000, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Still in ACTIVE after first USER_INTERACTION times out mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Both timed out, so NOTIFICATION_SEEN timeout should be effective mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_WORKING_SET); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_RARE); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testSystemInteractionTimeout() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); // Fast forward to RARE mInjector.mElapsedRealtime = RARE_THRESHOLD + 100; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RARE); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); // Trigger a SYSTEM_INTERACTION and verify bucket reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Verify it's still in ACTIVE close to end of timeout mInjector.mElapsedRealtime += mController.mSystemInteractionTimeoutMillis - 100; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Verify bucket moves to RARE after timeout mInjector.mElapsedRealtime += 200; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RARE); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testInitialForegroundServiceTimeout() throws Exception { mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100; // Make sure app is in NEVER bucket mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, REASON_MAIN_FORCED_BY_USER); mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_NEVER); + waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_1); mInjector.mElapsedRealtime += 100; // Trigger a FOREGROUND_SERVICE_START and verify bucket reportEvent(mController, FOREGROUND_SERVICE_START, mInjector.mElapsedRealtime, PACKAGE_1); mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Verify it's still in ACTIVE close to end of timeout mInjector.mElapsedRealtime += mController.mInitialForegroundServiceStartTimeoutMillis - 100; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Verify bucket moves to RARE after timeout mInjector.mElapsedRealtime += 200; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RARE); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); // Trigger a FOREGROUND_SERVICE_START again reportEvent(mController, FOREGROUND_SERVICE_START, mInjector.mElapsedRealtime, PACKAGE_1); mController.checkIdleStates(USER_ID); // Bucket should not be immediately elevated on subsequent service starts - assertBucket(STANDBY_BUCKET_RARE); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testPredictionNotOverridden() throws Exception { mInjector.mPropertiesChangedListener .onPropertiesChanged(mInjector.getDeviceConfigProperties()); reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime = WORKING_SET_THRESHOLD - 1000; reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Falls back to WORKING_SET mInjector.mElapsedRealtime += 5000; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_WORKING_SET); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); // Predict to ACTIVE mInjector.mElapsedRealtime += 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // CheckIdleStates should not change the prediction mInjector.mElapsedRealtime += 1000; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testPredictionStrikesBack() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Predict to FREQUENT mInjector.mElapsedRealtime = RARE_THRESHOLD; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_MAIN_PREDICTED); - assertBucket(STANDBY_BUCKET_FREQUENT); + waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1); // Add a short timeout event mInjector.mElapsedRealtime += 1000; reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime += 1000; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_ACTIVE); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); // Verify it reverted to predicted mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD / 2; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_FREQUENT); + waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testSystemForcedFlags_NotAddedForUserForce() throws Exception { final int expectedReason = REASON_MAIN_FORCED_BY_USER; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_USER); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); assertEquals(expectedReason, getStandbyBucketReason(PACKAGE_1)); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); assertEquals(expectedReason, getStandbyBucketReason(PACKAGE_1)); } @Test - @FlakyTest(bugId = 185169504) public void testSystemForcedFlags_AddedForSystemForce() throws Exception { mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_DEFAULT); @@ -1584,13 +1598,13 @@ public class AppStandbyControllerTests { mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE, getStandbyBucketReason(PACKAGE_1)); mController.restrictApp(PACKAGE_1, USER_ID, REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Flags should be combined assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE @@ -1598,7 +1612,6 @@ public class AppStandbyControllerTests { } @Test - @FlakyTest(bugId = 185169504) public void testSystemForcedFlags_SystemForceChangesBuckets() throws Exception { mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_DEFAULT); @@ -1607,14 +1620,14 @@ public class AppStandbyControllerTests { mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE); - assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1); assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE, getStandbyBucketReason(PACKAGE_1)); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE); - assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1); // Flags should be combined assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE @@ -1623,20 +1636,19 @@ public class AppStandbyControllerTests { mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY); - assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); // Flags should be combined assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY, getStandbyBucketReason(PACKAGE_1)); mController.restrictApp(PACKAGE_1, USER_ID, REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED); - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Flags should not be combined since the bucket changed. assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED, getStandbyBucketReason(PACKAGE_1)); } @Test - @FlakyTest(bugId = 185169504) public void testRestrictApp_MainReason() throws Exception { mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_DEFAULT); @@ -1644,11 +1656,11 @@ public class AppStandbyControllerTests { mController.restrictApp(PACKAGE_1, USER_ID, REASON_MAIN_PREDICTED, 0); // Call should be ignored. - assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mController.restrictApp(PACKAGE_1, USER_ID, REASON_MAIN_FORCED_BY_USER, 0); // Call should go through - assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); } @Test @@ -1724,15 +1736,15 @@ public class AppStandbyControllerTests { } @Test - @FlakyTest(bugId = 185169504) public void testUserInteraction_CrossProfile() throws Exception { mInjector.mRunningUsers = new int[] {USER_ID, USER_ID2, USER_ID3}; mInjector.mCrossProfileTargets = Arrays.asList(USER_HANDLE_USER2); reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertEquals("Cross profile connected package bucket should be elevated on usage", - STANDBY_BUCKET_ACTIVE, getStandbyBucket(USER_ID2, mController, PACKAGE_1)); - assertEquals("Not Cross profile connected package bucket should not be elevated on usage", - STANDBY_BUCKET_NEVER, getStandbyBucket(USER_ID3, mController, PACKAGE_1)); + waitAndAssertBucket("Cross profile connected package bucket should be elevated on usage", + mController, STANDBY_BUCKET_ACTIVE, USER_ID2, PACKAGE_1); + waitAndAssertBucket( + "Not Cross profile connected package bucket should not be elevated on usage", + mController, STANDBY_BUCKET_NEVER, USER_ID3, PACKAGE_1); assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE, USER_ID); assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE, USER_ID2); @@ -1742,51 +1754,50 @@ public class AppStandbyControllerTests { mInjector.mCrossProfileTargets = Collections.emptyList(); reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); - assertEquals("No longer cross profile connected package bucket should not be " - + "elevated on usage", - STANDBY_BUCKET_WORKING_SET, getStandbyBucket(USER_ID2, mController, PACKAGE_1)); + waitAndAssertBucket("No longer cross profile connected package bucket should not be " + + "elevated on usage", mController, STANDBY_BUCKET_WORKING_SET, USER_ID2, + PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testUnexemptedSyncScheduled() throws Exception { rearmLatch(PACKAGE_1); mController.addListener(mListener); - assertEquals("Test package did not start in the Never bucket", STANDBY_BUCKET_NEVER, - getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket("Test package did not start in the Never bucket", STANDBY_BUCKET_NEVER, + PACKAGE_1); mController.postReportSyncScheduled(PACKAGE_1, USER_ID, false); mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS); - assertEquals("Unexempted sync scheduled should bring the package out of the Never bucket", - STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket( + "Unexempted sync scheduled should bring the package out of the Never bucket", + STANDBY_BUCKET_WORKING_SET, PACKAGE_1); setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM); rearmLatch(PACKAGE_1); mController.postReportSyncScheduled(PACKAGE_1, USER_ID, false); mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS); - assertEquals("Unexempted sync scheduled should not elevate a non Never bucket", - STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket("Unexempted sync scheduled should not elevate a non Never bucket", + STANDBY_BUCKET_RARE, PACKAGE_1); } @Test - @FlakyTest(bugId = 185169504) public void testExemptedSyncScheduled() throws Exception { setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM); mInjector.mDeviceIdleMode = true; rearmLatch(PACKAGE_1); mController.postReportSyncScheduled(PACKAGE_1, USER_ID, true); mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS); - assertEquals("Exempted sync scheduled in doze should set bucket to working set", - STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket("Exempted sync scheduled in doze should set bucket to working set", + STANDBY_BUCKET_WORKING_SET, PACKAGE_1); setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM); mInjector.mDeviceIdleMode = false; rearmLatch(PACKAGE_1); mController.postReportSyncScheduled(PACKAGE_1, USER_ID, true); mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS); - assertEquals("Exempted sync scheduled while not in doze should set bucket to active", - STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket("Exempted sync scheduled while not in doze should set bucket to active", + STANDBY_BUCKET_ACTIVE, PACKAGE_1); } @Test @@ -1796,14 +1807,14 @@ public class AppStandbyControllerTests { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; mController.checkIdleStates(USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Updates shouldn't change bucket if the app was forced by the system for a non-buggy // reason. @@ -1814,11 +1825,11 @@ public class AppStandbyControllerTests { | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE); mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Updates should change bucket if the app was forced by the system for a buggy reason. reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); @@ -1827,11 +1838,11 @@ public class AppStandbyControllerTests { REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY); mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID); - assertNotEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertNotBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Updates shouldn't change bucket if the app was forced by the system for more than just // a buggy reason. @@ -1842,13 +1853,13 @@ public class AppStandbyControllerTests { | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY); mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE, getStandbyBucketReason(PACKAGE_1)); mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); // Updates shouldn't change bucket if the app was forced by the user. reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); @@ -1857,11 +1868,11 @@ public class AppStandbyControllerTests { REASON_MAIN_FORCED_BY_USER); mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID); - assertBucket(STANDBY_BUCKET_RESTRICTED); + waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1); } @Test @@ -1876,37 +1887,37 @@ public class AppStandbyControllerTests { mController.setAppStandbyBucket(PACKAGE_SYSTEM_HEADFULL, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_TIMEOUT); - assertBucket(STANDBY_BUCKET_RARE, PACKAGE_SYSTEM_HEADFULL); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_SYSTEM_HEADFULL); // Make sure headless system apps don't get lowered. mController.setAppStandbyBucket(PACKAGE_SYSTEM_HEADLESS, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_TIMEOUT); - assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_SYSTEM_HEADLESS); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_SYSTEM_HEADLESS); // Package 1 doesn't have activities and is headless, but is not a system app, so it can // be lowered. mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_TIMEOUT); - assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); } @Test public void testWellbeingAppElevated() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_WELLBEING); - assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_WELLBEING); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_WELLBEING); reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD; // Make sure the default wellbeing app does not get lowered below WORKING_SET. mController.setAppStandbyBucket(PACKAGE_WELLBEING, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_TIMEOUT); - assertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_WELLBEING); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_WELLBEING); // A non default wellbeing app should be able to fall lower than WORKING_SET. mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_TIMEOUT); - assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); } @Test @@ -1914,22 +1925,22 @@ public class AppStandbyControllerTests { mInjector.mClockApps.add(Pair.create(PACKAGE_1, UID_1)); reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); - assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_2); - assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_2); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_2); mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD; // Make sure a clock app does not get lowered below WORKING_SET. mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_TIMEOUT); - assertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); + waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1); // A non clock app should be able to fall lower than WORKING_SET. mController.setAppStandbyBucket(PACKAGE_2, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_TIMEOUT); - assertBucket(STANDBY_BUCKET_RARE, PACKAGE_2); + waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_2); } @Test @@ -2067,13 +2078,13 @@ public class AppStandbyControllerTests { public void testBackgroundLocationBucket() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_BACKGROUND_LOCATION); - assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_BACKGROUND_LOCATION); + waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_BACKGROUND_LOCATION); mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD; // Make sure PACKAGE_BACKGROUND_LOCATION does not get lowered than STANDBY_BUCKET_FREQUENT. mController.setAppStandbyBucket(PACKAGE_BACKGROUND_LOCATION, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_TIMEOUT); - assertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_BACKGROUND_LOCATION); + waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_BACKGROUND_LOCATION); } @Test @@ -2083,41 +2094,41 @@ public class AppStandbyControllerTests { mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_USER); - assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, mInjector.mLastNoteEvent); + waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_MAIN_FORCED_BY_USER); - assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, mInjector.mLastNoteEvent); + waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE); // Since we're staying on the PACKAGE_ACTIVE side, noteEvent shouldn't be called. // Reset the last event to confirm the method isn't called. mInjector.mLastNoteEvent = BatteryStats.HistoryItem.EVENT_NONE; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_FORCED_BY_USER); - assertEquals(BatteryStats.HistoryItem.EVENT_NONE, mInjector.mLastNoteEvent); + waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_NONE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_USER); - assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, mInjector.mLastNoteEvent); + waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE); // Since we're staying on the PACKAGE_ACTIVE side, noteEvent shouldn't be called. // Reset the last event to confirm the method isn't called. mInjector.mLastNoteEvent = BatteryStats.HistoryItem.EVENT_NONE; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_USER); - assertEquals(BatteryStats.HistoryItem.EVENT_NONE, mInjector.mLastNoteEvent); + waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_NONE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_MAIN_FORCED_BY_USER); - assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, mInjector.mLastNoteEvent); + waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, REASON_MAIN_FORCED_BY_USER); - assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, mInjector.mLastNoteEvent); + waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_EXEMPTED, REASON_MAIN_FORCED_BY_USER); - assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, mInjector.mLastNoteEvent); + waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE); } private String getAdminAppsStr(int userId) { @@ -2187,8 +2198,7 @@ public class AppStandbyControllerTests { rearmLatch(pkg); mController.setAppStandbyBucket(pkg, user, bucket, reason); mStateChangedLatch.await(1, TimeUnit.SECONDS); - assertEquals("Failed to set package bucket", bucket, - getStandbyBucket(mController, PACKAGE_1)); + waitAndAssertBucket("Failed to set package bucket", bucket, PACKAGE_1); } private void rearmLatch(String pkgName) { @@ -2205,4 +2215,12 @@ public class AppStandbyControllerTests { mLatchUserId = userId; mQuotaBumpLatch = new CountDownLatch(1); } + + private void flushHandler(AppStandbyController controller) { + assertTrue("Failed to flush handler!", controller.flushHandler(FLUSH_TIMEOUT_MILLISECONDS)); + // Some AppStandbyController handler messages queue another handler message. Flush again + // to catch those as well. + assertTrue("Failed to flush handler (the second time)!", + controller.flushHandler(FLUSH_TIMEOUT_MILLISECONDS)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 9a1359591890..a0e49a616a28 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -297,6 +297,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -12302,7 +12303,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { /* isImageBitmap= */ true, /* isExpired= */ true); addRecordAndRemoveBitmaps(record); - assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE)).isFalse(); + assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE)).isTrue(); + final Parcelable picture = record.getNotification().extras.getParcelable(EXTRA_PICTURE); + assertThat(picture).isNull(); } @Test @@ -12336,7 +12339,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { /* isImageBitmap= */ false, /* isExpired= */ true); addRecordAndRemoveBitmaps(record); - assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE_ICON)).isFalse(); + assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE_ICON)).isTrue(); + final Parcelable pictureIcon = + record.getNotification().extras.getParcelable(EXTRA_PICTURE_ICON); + assertThat(pictureIcon).isNull(); } @Test @@ -13732,6 +13738,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @Ignore("b/316989461") public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne() throws RemoteException { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags @@ -13761,6 +13768,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @Ignore("b/316989461") public void cancelNotificationsFromListener_rapidClear_old_cancelOne() throws RemoteException { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); @@ -13788,6 +13796,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @Ignore("b/316989461") public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne_flagDisabled() throws RemoteException { mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags @@ -13818,6 +13827,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @Ignore("b/316989461") public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll() throws RemoteException { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags @@ -13846,6 +13856,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @Ignore("b/316989461") public void cancelNotificationsFromListener_rapidClear_old_cancelAll() throws RemoteException { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); @@ -13872,6 +13883,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @Ignore("b/316989461") public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll_flagDisabled() throws RemoteException { mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java index 4a1435f9ee64..1fcee0658afc 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java @@ -99,7 +99,7 @@ public class ZenModeEventLoggerFake extends ZenModeEventLogger { public boolean getFromSystemOrSystemUi(int i) throws IllegalArgumentException { // While this isn't a logged output value, it's still helpful to check in tests. checkInRange(i); - return mChanges.get(i).mFromSystemOrSystemUi; + return mChanges.get(i).isFromSystemOrSystemUi(); } public boolean getIsUserAction(int i) throws IllegalArgumentException { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index bc63c29e9955..44f0894f76d7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -43,6 +43,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; import static android.provider.Settings.Global.ZEN_MODE_OFF; +import static android.service.notification.Condition.SOURCE_SCHEDULE; +import static android.service.notification.Condition.SOURCE_USER_ACTION; import static android.service.notification.Condition.STATE_FALSE; import static android.service.notification.Condition.STATE_TRUE; import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP; @@ -120,12 +122,13 @@ import android.service.notification.Condition; import android.service.notification.DeviceEffectsApplier; import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ConfigChangeOrigin; import android.service.notification.ZenModeConfig.ScheduleInfo; import android.service.notification.ZenModeConfig.ZenRule; import android.service.notification.ZenModeDiff; import android.service.notification.ZenPolicy; import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; +import android.testing.TestWithLooperRule; import android.testing.TestableLooper; import android.util.ArrayMap; import android.util.Log; @@ -148,6 +151,8 @@ import com.android.server.notification.ManagedServices.UserProfiles; import com.google.common.collect.ImmutableList; import com.google.common.truth.Correspondence; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; import org.junit.Before; import org.junit.Rule; @@ -173,7 +178,7 @@ import java.util.concurrent.TimeUnit; @SmallTest @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. -@RunWith(AndroidTestingRunner.class) +@RunWith(TestParameterInjector.class) @TestableLooper.RunWithLooper public class ZenModeHelperTest extends UiServiceTestCase { @@ -215,6 +220,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { public final SetFlagsRule mSetFlagsRule = new SetFlagsRule( SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); + @Rule(order = Integer.MAX_VALUE) // set the highest order so it's the innermost rule + public TestWithLooperRule mLooperRule = new TestWithLooperRule(); + ConditionProviders mConditionProviders; @Mock NotificationManager mNotificationManager; @Mock PackageManager mPackageManager; @@ -2341,15 +2349,38 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode); } + private enum ModesApiFlag { + ENABLED(true, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_USER), + DISABLED(false, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI); + + private final boolean mEnabled; + @ConfigChangeOrigin private final int mOriginForUserActionInSystemUi; + + ModesApiFlag(boolean enabled, @ConfigChangeOrigin int originForUserActionInSystemUi) { + this.mEnabled = enabled; + this.mOriginForUserActionInSystemUi = originForUserActionInSystemUi; + } + + void applyFlag(SetFlagsRule setFlagsRule) { + if (mEnabled) { + setFlagsRule.enableFlags(Flags.FLAG_MODES_API); + } else { + setFlagsRule.disableFlags(Flags.FLAG_MODES_API); + } + } + } + @Test - public void testZenModeEventLog_setManualZenMode() throws IllegalArgumentException { + public void testZenModeEventLog_setManualZenMode(@TestParameter ModesApiFlag modesApiFlag) + throws IllegalArgumentException { + modesApiFlag.applyFlag(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); // Turn zen mode on (to important_interruptions) // Need to additionally call the looper in order to finish the post-apply-config process mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, - UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID); + modesApiFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID); // Now turn zen mode off, but via a different package UID -- this should get registered as // "not an action by the user" because some other app is changing zen mode @@ -2376,7 +2407,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0)); assertEquals(DNDProtoEnums.MANUAL_RULE, mZenModeEventLogger.getChangedRuleType(0)); assertEquals(1, mZenModeEventLogger.getNumRulesActive(0)); - assertTrue(mZenModeEventLogger.getFromSystemOrSystemUi(0)); + assertThat(mZenModeEventLogger.getFromSystemOrSystemUi(0)).isEqualTo( + modesApiFlag == ModesApiFlag.DISABLED); assertTrue(mZenModeEventLogger.getIsUserAction(0)); assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0)); checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0)); @@ -2401,7 +2433,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testZenModeEventLog_automaticRules() throws IllegalArgumentException { + public void testZenModeEventLog_automaticRules(@TestParameter ModesApiFlag modesApiFlag) + throws IllegalArgumentException { + modesApiFlag.applyFlag(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); @@ -2423,8 +2457,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Event 2: "User" turns off the automatic rule (sets it to not enabled) zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", - Process.SYSTEM_UID); + mZenModeHelper.updateAutomaticZenRule(id, zenRule, + modesApiFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID); // Add a new system rule AutomaticZenRule systemRule = new AutomaticZenRule("systemRule", @@ -2442,8 +2476,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Event 4: "User" deletes the rule - mZenModeHelper.removeAutomaticZenRule(systemId, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", - Process.SYSTEM_UID); + mZenModeHelper.removeAutomaticZenRule(systemId, modesApiFlag.mOriginForUserActionInSystemUi, + "", Process.SYSTEM_UID); // In total, this represents 4 events assertEquals(4, mZenModeEventLogger.numLoggedChanges()); @@ -2499,20 +2533,109 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testZenModeEventLog_policyChanges() throws IllegalArgumentException { + @EnableFlags(Flags.FLAG_MODES_API) + public void testZenModeEventLog_automaticRuleActivatedFromAppByAppAndUser() + throws IllegalArgumentException { + mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); + setupZenConfig(); + + // Ann app adds an automatic zen rule + AutomaticZenRule zenRule = new AutomaticZenRule("name", + null, + new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"), + ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), + null, + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID); + + // Event 1: Mimic the rule coming on manually when the user turns it on in the app + // ("Turn on bedtime now" because user goes to bed earlier). + mZenModeHelper.setAutomaticZenRuleState(id, + new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_USER_ACTION), + UPDATE_ORIGIN_USER, CUSTOM_PKG_UID); + + // Event 2: App deactivates the rule automatically (it's 8 AM, bedtime schedule ends) + mZenModeHelper.setAutomaticZenRuleState(id, + new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE), + UPDATE_ORIGIN_APP, CUSTOM_PKG_UID); + + // Event 3: App activates the rule automatically (it's now 11 PM, bedtime schedule starts) + mZenModeHelper.setAutomaticZenRuleState(id, + new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE), + UPDATE_ORIGIN_APP, CUSTOM_PKG_UID); + + // Event 4: User deactivates the rule manually (they get up before 8 AM on the next day) + mZenModeHelper.setAutomaticZenRuleState(id, + new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_USER_ACTION), + UPDATE_ORIGIN_USER, CUSTOM_PKG_UID); + + // In total, this represents 4 events + assertEquals(4, mZenModeEventLogger.numLoggedChanges()); + + // Automatic rule turning on manually: + // - event ID: DND_TURNED_ON + // - 1 rule (newly) active + // - is a user action + // - package UID is the calling package + assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(), + mZenModeEventLogger.getEventId(0)); + assertEquals(1, mZenModeEventLogger.getNumRulesActive(0)); + assertTrue(mZenModeEventLogger.getIsUserAction(0)); + assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0)); + + // Automatic rule turned off automatically by app: + // - event ID: DND_TURNED_OFF + // - 0 rules active + // - is not a user action + // - package UID is the calling package + assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(), + mZenModeEventLogger.getEventId(1)); + assertEquals(0, mZenModeEventLogger.getNumRulesActive(1)); + assertFalse(mZenModeEventLogger.getIsUserAction(1)); + assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1)); + + // Automatic rule turned on automatically by app: + // - event ID: DND_TURNED_ON + // - 1 rule (newly) active + // - is not a user action + // - package UID is the calling package + assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(), + mZenModeEventLogger.getEventId(2)); + assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(2)); + assertEquals(1, mZenModeEventLogger.getNumRulesActive(2)); + assertFalse(mZenModeEventLogger.getIsUserAction(2)); + assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(2)); + + // Automatic rule turned off automatically by the user: + // - event ID: DND_TURNED_ON + // - 0 rules active + // - is a user action + // - package UID is the calling package + assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(), + mZenModeEventLogger.getEventId(3)); + assertEquals(0, mZenModeEventLogger.getNumRulesActive(3)); + assertTrue(mZenModeEventLogger.getIsUserAction(3)); + assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(3)); + } + + @Test + public void testZenModeEventLog_policyChanges(@TestParameter ModesApiFlag modesApiFlag) + throws IllegalArgumentException { + modesApiFlag.applyFlag(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); // First just turn zen mode on mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, - UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID); + modesApiFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID); // Now change the policy slightly; want to confirm that this'll be reflected in the logs ZenModeConfig newConfig = mZenModeHelper.mConfig.copy(); newConfig.allowAlarms = true; newConfig.allowRepeatCallers = false; mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), - UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); + modesApiFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID); // Turn zen mode off; we want to make sure policy changes do not get logged when zen mode // is off. @@ -2523,7 +2646,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { newConfig.allowMessages = false; newConfig.allowRepeatCallers = true; mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), - UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); + modesApiFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID); // Total events: we only expect ones for turning on, changing policy, and turning off assertEquals(3, mZenModeEventLogger.numLoggedChanges()); @@ -2556,7 +2679,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testZenModeEventLog_ruleCounts() throws IllegalArgumentException { + public void testZenModeEventLog_ruleCounts(@TestParameter ModesApiFlag modesApiFlag) + throws IllegalArgumentException { + modesApiFlag.applyFlag(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); @@ -2659,8 +2784,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testZenModeEventLog_noLogWithNoConfigChange() throws IllegalArgumentException { + public void testZenModeEventLog_noLogWithNoConfigChange( + @TestParameter ModesApiFlag modesApiFlag) throws IllegalArgumentException { // If evaluateZenMode is called independently of a config change, don't log. + modesApiFlag.applyFlag(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); @@ -2677,9 +2804,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testZenModeEventLog_reassignUid() throws IllegalArgumentException { + public void testZenModeEventLog_reassignUid(@TestParameter ModesApiFlag modesApiFlag) + throws IllegalArgumentException { // Test that, only in specific cases, we reassign the calling UID to one associated with // the automatic rule owner. + modesApiFlag.applyFlag(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); @@ -2691,7 +2820,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, - UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); + UPDATE_ORIGIN_APP, "test", Process.SYSTEM_UID); // Rule 2, same as rule 1 but owned by the system AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", @@ -2701,11 +2830,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, - UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); + modesApiFlag.mOriginForUserActionInSystemUi, "test", Process.SYSTEM_UID); // Turn on rule 1; call looks like it's from the system. Because setting a condition is // typically an automatic (non-user-initiated) action, expect the calling UID to be - // re-evaluated to the one associat.d with CUSTOM_PKG_NAME. + // re-evaluated to the one associated with CUSTOM_PKG_NAME. mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); @@ -2719,8 +2848,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Disable rule 1. Because this looks like a user action, the UID should not be modified // from the system-provided one. zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", - Process.SYSTEM_UID); + mZenModeHelper.updateAutomaticZenRule(id, zenRule, + modesApiFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID); // Add a manual rule. Any manual rule changes should not get calling uids reassigned. mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP, @@ -2777,8 +2906,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testZenModeEventLog_channelsBypassingChanges() { + public void testZenModeEventLog_channelsBypassingChanges( + @TestParameter ModesApiFlag modesApiFlag) { // Verify that the right thing happens when the canBypassDnd value changes. + modesApiFlag.applyFlag(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); @@ -2874,14 +3005,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Second message where we change the policy: // - DND_POLICY_CHANGED (indicates only the policy changed and nothing else) // - rule type: unknown (it's a policy change, not a rule change) - // - user action (because it comes from a "system" uid) // - change is in allow channels, and final policy assertThat(mZenModeEventLogger.getEventId(1)) .isEqualTo(ZenModeEventLogger.ZenStateChangedEvent.DND_POLICY_CHANGED.getId()); assertThat(mZenModeEventLogger.getChangedRuleType(1)) .isEqualTo(DNDProtoEnums.UNKNOWN_RULE); - assertThat(mZenModeEventLogger.getIsUserAction(1)).isTrue(); - assertThat(mZenModeEventLogger.getPackageUid(1)).isEqualTo(Process.SYSTEM_UID); DNDPolicyProto dndProto = mZenModeEventLogger.getPolicyProto(1); assertThat(dndProto.getAllowChannels().getNumber()) .isEqualTo(DNDProtoEnums.CHANNEL_TYPE_NONE); diff --git a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java index d2ef1808652f..ca3787ec4546 100644 --- a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java @@ -95,6 +95,26 @@ public final class DeferredKeyActionExecutorTests { assertFalse(action.executed); } + @Test + public void queueKeyAction_beforeAndAfterCancelQueuedActions_onlyActionsAfterCancelExecuted() { + TestAction action1 = new TestAction(); + TestAction action2 = new TestAction(); + TestAction action3 = new TestAction(); + mKeyActionExecutor.queueKeyAction( + KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action1); + mKeyActionExecutor.queueKeyAction( + KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action2); + mKeyActionExecutor.cancelQueuedAction(KeyEvent.KEYCODE_STEM_PRIMARY); + mKeyActionExecutor.queueKeyAction( + KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action3); + + mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1); + + assertFalse(action1.executed); + assertFalse(action2.executed); + assertTrue(action3.executed); + } + static class TestAction implements Runnable { public boolean executed; diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java index f7ad2a8f5243..50d37ec7749b 100644 --- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java @@ -19,14 +19,18 @@ package com.android.server.policy; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS; +import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS; import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.server.policy.PhoneWindowManager.DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP; import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT; import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS; import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY; +import static com.android.server.policy.PhoneWindowManager.TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY; import android.app.ActivityManager.RecentTaskInfo; +import android.app.ActivityTaskManager.RootTaskInfo; import android.content.ComponentName; import android.os.RemoteException; import android.provider.Settings; @@ -50,6 +54,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { public void stemSingleKey_duringSetup_doNothing() { overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(false); @@ -65,6 +70,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { public void stemSingleKey_AfterSetup_openAllApp() { overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.overrideStartActivity(); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); @@ -83,6 +89,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.overrideStartActivity(); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); @@ -104,6 +111,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(true); + setDispatchedKeyHandler(keyEvent -> true); sendKey(KEYCODE_STEM_PRIMARY); @@ -131,6 +139,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { STEM_PRIMARY_BUTTON_LONG_PRESS, LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.setupAssistForLaunch(); mPhoneWindowManager.overrideIsUserSetupComplete(true); @@ -144,6 +153,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { STEM_PRIMARY_BUTTON_LONG_PRESS, LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.setupAssistForLaunch(); mPhoneWindowManager.overrideSearchManager(null); mPhoneWindowManager.overrideStatusBarManagerInternal(); @@ -156,7 +166,8 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { @Test public void stemDoubleKey_EarlyShortPress_AllAppsThenSwitchToMostRecent() throws RemoteException { - overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); + overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); + overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); @@ -171,14 +182,47 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertOpenAllAppView(); - mPhoneWindowManager.assertSwitchToRecent(referenceId); + mPhoneWindowManager.assertSwitchToTask(referenceId); } @Test - public void stemDoubleKey_NoEarlyShortPress_SwitchToMostRecent() throws RemoteException { + public void stemTripleKey_EarlyShortPress_AllAppsThenBackToOriginalThenToggleA11y() + throws RemoteException { + overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); + overrideBehavior( + STEM_PRIMARY_BUTTON_TRIPLE_PRESS, TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY); + setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true); + mPhoneWindowManager.overrideTalkbackShortcutGestureEnabled(true); + mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); + mPhoneWindowManager.overrideIsUserSetupComplete(true); + RootTaskInfo allAppsTask = new RootTaskInfo(); + int referenceId = 777; + allAppsTask.taskId = referenceId; + doReturn(allAppsTask) + .when(mPhoneWindowManager.mActivityManagerService) + .getFocusedRootTaskInfo(); + + mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ false); + + sendKey(KEYCODE_STEM_PRIMARY); + sendKey(KEYCODE_STEM_PRIMARY); + sendKey(KEYCODE_STEM_PRIMARY); + + mPhoneWindowManager.assertOpenAllAppView(); + mPhoneWindowManager.assertSwitchToTask(referenceId); + mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true); + } + + @Test + public void stemMultiKey_NoEarlyPress_NoOpenAllApp() throws RemoteException { + overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); + overrideBehavior( + STEM_PRIMARY_BUTTON_TRIPLE_PRESS, TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); + mPhoneWindowManager.overrideTalkbackShortcutGestureEnabled(true); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); RecentTaskInfo recentTaskInfo = new RecentTaskInfo(); @@ -189,9 +233,16 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { sendKey(KEYCODE_STEM_PRIMARY); sendKey(KEYCODE_STEM_PRIMARY); + sendKey(KEYCODE_STEM_PRIMARY); + + mPhoneWindowManager.assertNotOpenAllAppView(); + mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true); + + sendKey(KEYCODE_STEM_PRIMARY); + sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertNotOpenAllAppView(); - mPhoneWindowManager.assertSwitchToRecent(referenceId); + mPhoneWindowManager.assertSwitchToTask(referenceId); } @Test @@ -215,7 +266,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertNotOpenAllAppView(); - mPhoneWindowManager.assertSwitchToRecent(referenceId); + mPhoneWindowManager.assertSwitchToTask(referenceId); } private void overrideBehavior(String key, int expectedBehavior) { diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 0678210e1d2f..7c2f7eedff9d 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -73,6 +73,7 @@ import android.media.AudioManagerInternal; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.Looper; import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.RemoteException; @@ -176,8 +177,9 @@ class TestPhoneWindowManager { private Handler mHandler; private boolean mIsTalkBackEnabled; + private boolean mIsTalkBackShortcutGestureEnabled; - class TestTalkbackShortcutController extends TalkbackShortcutController { + private class TestTalkbackShortcutController extends TalkbackShortcutController { TestTalkbackShortcutController(Context context) { super(context); } @@ -190,13 +192,18 @@ class TestPhoneWindowManager { @Override boolean isTalkBackShortcutGestureEnabled() { - return true; + return mIsTalkBackShortcutGestureEnabled; } } private class TestInjector extends PhoneWindowManager.Injector { TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) { - super(context, funcs, mTestLooper.getLooper()); + super(context, funcs); + } + + @Override + Looper getLooper() { + return mTestLooper.getLooper(); } AccessibilityShortcutController getAccessibilityShortcutController( @@ -410,6 +417,10 @@ class TestPhoneWindowManager { mPhoneWindowManager.mShouldEarlyShortPressOnStemPrimary = shouldEarlyShortPress; } + void overrideTalkbackShortcutGestureEnabled(boolean enabled) { + mIsTalkBackShortcutGestureEnabled = enabled; + } + // Override assist perform function. void overrideLongPressOnPower(int behavior) { mPhoneWindowManager.mLongPressOnPowerBehavior = behavior; @@ -714,7 +725,7 @@ class TestPhoneWindowManager { } void assertOpenAllAppView() { - mTestLooper.dispatchAll(); + moveTimeForward(TEST_SINGLE_KEY_DELAY_MILLIS); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)) .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class)); @@ -728,7 +739,7 @@ class TestPhoneWindowManager { } void assertActivityTargetLaunched(ComponentName targetActivity) { - mTestLooper.dispatchAll(); + moveTimeForward(TEST_SINGLE_KEY_DELAY_MILLIS); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)) .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class)); @@ -743,10 +754,15 @@ class TestPhoneWindowManager { expectedModifierState, deviceBus), description(errorMsg)); } - void assertSwitchToRecent(int persistentId) throws RemoteException { + void assertSwitchToTask(int persistentId) throws RemoteException { mTestLooper.dispatchAll(); verify(mActivityManagerService, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)).startActivityFromRecents(eq(persistentId), isNull()); } + + void assertTalkBack(boolean expectEnabled) { + mTestLooper.dispatchAll(); + Assert.assertEquals(expectEnabled, mIsTalkBackEnabled); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java index ef427bb15039..a8b217860946 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java @@ -16,7 +16,7 @@ package com.android.server.wm; -import static android.server.wm.CtsWindowInfoUtils.dumpWindowsOnScreen; +import static android.server.wm.CtsWindowInfoUtils.assertAndDumpWindowState; import static android.server.wm.CtsWindowInfoUtils.waitForWindowFocus; import static android.server.wm.CtsWindowInfoUtils.waitForWindowVisible; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -129,37 +129,22 @@ public class SurfaceControlViewHostTests { mScvh2.setView(mView2, lp2); }); - boolean wasVisible = waitForWindowVisible(mView1); - if (!wasVisible) { - dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows"); - } - assertTrue("Failed to wait for view1", wasVisible); - - wasVisible = waitForWindowVisible(mView2); - if (!wasVisible) { - dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-not visible"); - } - assertTrue("Failed to wait for view2", wasVisible); + assertAndDumpWindowState(TAG, "Failed to wait for view1", waitForWindowVisible(mView1)); + assertAndDumpWindowState(TAG, "Failed to wait for view2", waitForWindowVisible(mView2)); IWindow window = IWindow.Stub.asInterface(mSurfaceView.getWindowToken()); WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window, mScvh1.getInputTransferToken(), true); - boolean gainedFocus = waitForWindowFocus(mView1, true); - if (!gainedFocus) { - dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-view1 not focus"); - } - assertTrue("Failed to gain focus for view1", gainedFocus); + assertAndDumpWindowState(TAG, "Failed to wait for view1 focus", + waitForWindowFocus(mView1, true)); WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window, mScvh2.getInputTransferToken(), true); - gainedFocus = waitForWindowFocus(mView2, true); - if (!gainedFocus) { - dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-view2 not focus"); - } - assertTrue("Failed to gain focus for view2", gainedFocus); + assertAndDumpWindowState(TAG, "Failed to wait for view2 focus", + waitForWindowFocus(mView2, true)); } private static class TestWindowlessWindowManager extends WindowlessWindowManager { diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java index ac498397eb39..6a15b0594428 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.server.wm.CtsWindowInfoUtils.assertAndDumpWindowState; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; @@ -138,11 +139,8 @@ public class TrustedOverlayTests { return false; }, TIMEOUT_S, TimeUnit.SECONDS); - if (!foundTrusted[0]) { - CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName.getMethodName()); - } - - assertTrue("Failed to find window or was not marked trusted", foundTrusted[0]); + assertAndDumpWindowState(TAG, "Failed to find window or was not marked trusted", + foundTrusted[0]); } private void testTrustedOverlayChildHelper(boolean expectedTrustedChild) diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index def52a517913..874c10c8ea83 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -210,100 +210,6 @@ public final class Call { "android.telecom.extra.SILENT_RINGING_REQUESTED"; /** - * Call event sent from a {@link Call} via {@link #sendCallEvent(String, Bundle)} to inform - * Telecom that the user has requested that the current {@link Call} should be handed over - * to another {@link ConnectionService}. - * <p> - * The caller must specify the {@link #EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE} to indicate to - * Telecom which {@link PhoneAccountHandle} the {@link Call} should be handed over to. - * @hide - * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated - * APIs instead. - */ - public static final String EVENT_REQUEST_HANDOVER = - "android.telecom.event.REQUEST_HANDOVER"; - - /** - * Extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Specifies the - * {@link PhoneAccountHandle} to which a call should be handed over to. - * @hide - * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated - * APIs instead. - */ - public static final String EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE = - "android.telecom.extra.HANDOVER_PHONE_ACCOUNT_HANDLE"; - - /** - * Integer extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Specifies the - * video state of the call when it is handed over to the new {@link PhoneAccount}. - * <p> - * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY}, - * {@link VideoProfile#STATE_BIDIRECTIONAL}, {@link VideoProfile#STATE_RX_ENABLED}, and - * {@link VideoProfile#STATE_TX_ENABLED}. - * @hide - * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated - * APIs instead. - */ - public static final String EXTRA_HANDOVER_VIDEO_STATE = - "android.telecom.extra.HANDOVER_VIDEO_STATE"; - - /** - * Extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Used by the - * {@link InCallService} initiating a handover to provide a {@link Bundle} with extra - * information to the handover {@link ConnectionService} specified by - * {@link #EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE}. - * <p> - * This {@link Bundle} is not interpreted by Telecom, but passed as-is to the - * {@link ConnectionService} via the request extras when - * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)} - * is called to initate the handover. - * @hide - * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated - * APIs instead. - */ - public static final String EXTRA_HANDOVER_EXTRAS = "android.telecom.extra.HANDOVER_EXTRAS"; - - /** - * Call event sent from Telecom to the handover {@link ConnectionService} via - * {@link Connection#onCallEvent(String, Bundle)} to inform a {@link Connection} that a handover - * to the {@link ConnectionService} has completed successfully. - * <p> - * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event. - * @hide - * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated - * APIs instead. - */ - public static final String EVENT_HANDOVER_COMPLETE = - "android.telecom.event.HANDOVER_COMPLETE"; - - /** - * Call event sent from Telecom to the handover destination {@link ConnectionService} via - * {@link Connection#onCallEvent(String, Bundle)} to inform the handover destination that the - * source connection has disconnected. The {@link Bundle} parameter for the call event will be - * {@code null}. - * <p> - * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event. - * @hide - * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated - * APIs instead. - */ - public static final String EVENT_HANDOVER_SOURCE_DISCONNECTED = - "android.telecom.event.HANDOVER_SOURCE_DISCONNECTED"; - - /** - * Call event sent from Telecom to the handover {@link ConnectionService} via - * {@link Connection#onCallEvent(String, Bundle)} to inform a {@link Connection} that a handover - * to the {@link ConnectionService} has failed. - * <p> - * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event. - * @hide - * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated - * APIs instead. - */ - public static final String EVENT_HANDOVER_FAILED = - "android.telecom.event.HANDOVER_FAILED"; - - /** * Event reported from the Telecom stack to report an in-call diagnostic message which the * dialer app may opt to display to the user. A diagnostic message is used to communicate * scenarios the device has detected which may impact the quality of the ongoing call. diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 4a541da9e5aa..ee9bf8983900 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -961,28 +961,6 @@ public abstract class Connection extends Conferenceable { "android.telecom.event.CALL_REMOTELY_UNHELD"; /** - * Connection event used to inform an {@link InCallService} which initiated a call handover via - * {@link Call#EVENT_REQUEST_HANDOVER} that the handover from this {@link Connection} has - * successfully completed. - * @hide - * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated - * APIs instead. - */ - public static final String EVENT_HANDOVER_COMPLETE = - "android.telecom.event.HANDOVER_COMPLETE"; - - /** - * Connection event used to inform an {@link InCallService} which initiated a call handover via - * {@link Call#EVENT_REQUEST_HANDOVER} that the handover from this {@link Connection} has failed - * to complete. - * @hide - * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated - * APIs instead. - */ - public static final String EVENT_HANDOVER_FAILED = - "android.telecom.event.HANDOVER_FAILED"; - - /** * String Connection extra key used to store SIP invite fields for an incoming call for IMS call */ public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE"; diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java index db38f8873a02..575ec27622a5 100644 --- a/telephony/java/android/telephony/AnomalyReporter.java +++ b/telephony/java/android/telephony/AnomalyReporter.java @@ -187,14 +187,15 @@ public final class AnomalyReporter { } for (ResolveInfo r : packages) { - if (r.activityInfo == null - || pm.checkPermission( + if (r.activityInfo == null) { + Rlog.w(TAG, "Found package without activity"); + continue; + } else if (pm.checkPermission( android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, r.activityInfo.packageName) - != PackageManager.PERMISSION_GRANTED) { - Rlog.w(TAG, - "Found package without proper permissions or no activity" - + r.activityInfo.packageName); + != PackageManager.PERMISSION_GRANTED) { + Rlog.w(TAG, "Found package without proper permissions" + + r.activityInfo.packageName); continue; } Rlog.d(TAG, "Found a valid package " + r.activityInfo.packageName); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index bcd99295b605..c7b84a3b9530 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -525,6 +525,12 @@ public class CarrierConfigManager { public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; /** + * Used in the Preferred Network Types menu to determine if the 3G option is displayed. + */ + @FlaggedApi(Flags.FLAG_HIDE_PREFER_3G_ITEM) + public static final String KEY_PREFER_3G_VISIBILITY_BOOL = "prefer_3g_visibility_bool"; + + /** * Used in Cellular Network Settings for preferred network type to show 4G only mode. * @hide */ @@ -3715,19 +3721,19 @@ public class CarrierConfigManager { * This configuration allows the system UI to display different 5G icons for different 5G * scenarios. * - * There are five 5G scenarios: - * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using - * millimeter wave. - * 2. connected: device currently connected to 5G cell as the secondary cell but not using - * millimeter wave. - * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability(not necessary - * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC - * currently in IDLE state. - * 4. not_restricted_rrc_con: device camped on a network that has 5G capability(not necessary - * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC - * currently in CONNECTED state. - * 5. restricted: device camped on a network that has 5G capability(not necessary to connect a - * 5G cell as a secondary cell) but the use of 5G is restricted. + * There are six 5G scenarios for icon configuration: + * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell + * and considered NR advanced. + * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not + * considered NR advanced. + * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell + * and RRC currently in IDLE state. + * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of + * 5G is not restricted and RRC currently in IDLE state. + * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of + * 5G is not restricted and RRC currently in CONNECTED state. + * 6. restricted: device camped on a network that has 5G capability but the use of 5G is + * restricted. * * The configured string contains multiple key-value pairs separated by comma. For each pair, * the key and value are separated by a colon. The key corresponds to a 5G status above and @@ -3748,21 +3754,21 @@ public class CarrierConfigManager { * This configuration allows the system UI to determine how long to continue to display 5G icons * when the device switches between different 5G scenarios. * - * There are seven 5G scenarios: - * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using - * millimeter wave. - * 2. connected: device currently connected to 5G cell as the secondary cell but not using - * millimeter wave. - * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability (not necessary - * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC - * currently in IDLE state. - * 4. not_restricted_rrc_con: device camped on a network that has 5G capability (not necessary - * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC - * currently in CONNECTED state. - * 5. restricted: device camped on a network that has 5G capability (not necessary to connect a - * 5G cell as a secondary cell) but the use of 5G is restricted. - * 6. legacy: device is not camped on a network that has 5G capability - * 7. any: any of the above scenarios + * There are eight 5G scenarios: + * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell + * and considered NR advanced. + * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not + * considered NR advanced. + * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell + * and RRC currently in IDLE state. + * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of + * 5G is not restricted and RRC currently in IDLE state. + * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of + * 5G is not restricted and RRC currently in CONNECTED state. + * 6. restricted: device camped on a network that has 5G capability but the use of 5G is + * restricted. + * 7. legacy: device is not camped on a network that has 5G capability + * 8. any: any of the above scenarios * * The configured string contains various timer rules separated by a semicolon. * Each rule will have three items: prior 5G scenario, current 5G scenario, and grace period @@ -3770,8 +3776,8 @@ public class CarrierConfigManager { * 5G scenario, the system UI will continue to show the icon for the prior 5G scenario (defined * in {@link #KEY_5G_ICON_CONFIGURATION_STRING}) for the amount of time specified by the grace * period. If the prior 5G scenario is reestablished, the timer will reset and start again if - * the UE changes 5G scenarios again. Defined states (5G scenarios #1-5) take precedence over - * 'any' (5G scenario #6), and unspecified transitions have a default grace period of 0. + * the UE changes 5G scenarios again. Defined states (5G scenarios #1-7) take precedence over + * 'any' (5G scenario #8), and unspecified transitions have a default grace period of 0. * The order of rules in the configuration determines the priority (the first applicable timer * rule will be used). * @@ -3794,21 +3800,21 @@ public class CarrierConfigManager { * This configuration extends {@link #KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING} to allow the * system UI to continue displaying 5G icons after the initial timer expires. * - * There are seven 5G scenarios: - * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using - * millimeter wave. - * 2. connected: device currently connected to 5G cell as the secondary cell but not using - * millimeter wave. - * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability (not necessary - * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC - * currently in IDLE state. - * 4. not_restricted_rrc_con: device camped on a network that has 5G capability (not necessary - * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC - * currently in CONNECTED state. - * 5. restricted: device camped on a network that has 5G capability (not necessary to connect a - * 5G cell as a secondary cell) but the use of 5G is restricted. - * 6. legacy: device is not camped on a network that has 5G capability - * 7. any: any of the above scenarios + * There are eight 5G scenarios: + * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell + * and considered NR advanced. + * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not + * considered NR advanced. + * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell + * and RRC currently in IDLE state. + * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of + * 5G is not restricted and RRC currently in IDLE state. + * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of + * 5G is not restricted and RRC currently in CONNECTED state. + * 6. restricted: device camped on a network that has 5G capability but the use of 5G is + * restricted. + * 7. legacy: device is not camped on a network that has 5G capability + * 8. any: any of the above scenarios * * The configured string contains various timer rules separated by a semicolon. * Each rule will have three items: primary 5G scenario, secondary 5G scenario, and @@ -3818,7 +3824,7 @@ public class CarrierConfigManager { * period. If the primary 5G scenario is reestablished, the timers will reset and the system UI * will continue to display the icon for the primary 5G scenario without interruption. If the * secondary 5G scenario is lost, the timer will reset and the icon will reflect the true state. - * Defined states (5G scenarios #1-5) take precedence over 'any' (5G scenario #6), and + * Defined states (5G scenarios #1-7) take precedence over 'any' (5G scenario #8), and * unspecified transitions have a default grace period of 0. The order of rules in the * configuration determines the priority (the first applicable timer rule will be used). * @@ -8885,18 +8891,18 @@ public class CarrierConfigManager { KEY_PREFIX + "epdg_static_address_roaming_string"; /** - * Controls if the multiple SA proposals allowed for IKE session to include - * all the 3GPP TS 33.210 and RFC 8221 supported cipher suites in multiple - * IKE SA proposals as per RFC 7296. + * Enables the use of multiple IKE SA proposals, encompassing both carrier-preferred + * ciphers and all supported ciphers from 3GPP TS 33.210 and RFC 8221, + * as defined in RFC 7296. */ @FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS) public static final String KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL = KEY_PREFIX + "supports_ike_session_multiple_sa_proposals_bool"; /** - * Controls if the multiple SA proposals allowed for Child session to include - * all the 3GPP TS 33.210 and RFC 8221 supported cipher suites in multiple - * Child SA proposals as per RFC 7296. + * Enables the use of multiple Child SA proposals, encompassing both carrier-preferred + * ciphers and all supported ciphers from 3GPP TS 33.210 and RFC 8221, + * as defined in RFC 7296. */ @FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS) public static final String KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL = @@ -10044,6 +10050,49 @@ public class CarrierConfigManager { public static final String KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_BUNDLE = "auto_data_switch_rat_signal_score_string_bundle"; + // TODO(b/316183370): replace @code with @link in javadoc after feature is released + /** + * An array of cellular services supported by a subscription. + * + * <p>Permissible values include: + * <ul> + * <li>{@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} for voice services</li> + * <li>{@code SubscriptionManager#SERVICE_CAPABILITY_SMS} for SMS services</li> + * <li>{@code SubscriptionManager#SERVICE_CAPABILITY_DATA} for data services</li> + * </ul> + * + * <p>Carrier-specific factors may influence how these services are supported. Therefore, + * modifying this carrier configuration might not always enable the specified services. These + * capability bitmasks should be considered as indicators of a carrier's preferred services + * to enhance user experience, rather than as absolute platform guarantees. + * + * <p>Device-level service capabilities, defined by + * {@code TelephonyManager#isDeviceVoiceCapable} and + * {@code TelephonyManager#isDeviceSmsCapable}, take precedence over these subscription-level + * settings. For instance, a device where {@code TelephonyManager#isDeviceVoiceCapable} returns + * false may not be able to make voice calls, even if subscribed to a service marked as + * voice-capable. + * + * <p>To determine a subscription's cellular service capabilities, use + * {@code SubscriptionInfo#getServiceCapabilities()}. To track changes in services, register + * a {@link SubscriptionManager.OnSubscriptionsChangedListener} and invoke the + * same method in its callback. + * + * <p>Emergency service availability may not depend on the cellular service capabilities. + * For example, emergency calls might be possible on a subscription even if it lacks + * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE}. + * + * <p>If unset, the default value is “[1, 2, 3]” (supports all cellular services). + * + * @see TelephonyManager#isDeviceVoiceCapable + * @see TelephonyManager#isDeviceSmsCapable + * @see SubscriptionInfo#getServiceCapabilities() + * @see SubscriptionManager.OnSubscriptionsChangedListener + */ + @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) + public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY = + "cellular_service_capabilities_int_array"; + /** The default value for every variable. */ private static final PersistableBundle sDefaults; @@ -10144,6 +10193,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL, false); sDefaults.putBoolean(KEY_OPERATOR_SELECTION_EXPAND_BOOL, true); sDefaults.putBoolean(KEY_PREFER_2G_BOOL, false); + sDefaults.putBoolean(KEY_PREFER_3G_VISIBILITY_BOOL, true); sDefaults.putBoolean(KEY_4G_ONLY_BOOL, false); sDefaults.putBoolean(KEY_SHOW_APN_SETTING_CDMA_BOOL, false); sDefaults.putBoolean(KEY_SHOW_CDMA_CHOICES_BOOL, false); @@ -10558,7 +10608,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_USE_CALL_WAITING_USSD_BOOL, false); sDefaults.putInt(KEY_CALL_WAITING_SERVICE_CLASS_INT, 1 /* SERVICE_CLASS_VOICE */); sDefaults.putString(KEY_5G_ICON_CONFIGURATION_STRING, - "connected_mmwave:5G,connected:5G,not_restricted_rrc_idle:5G," + "connected_mmwave:5G,connected:5G,connected_rrc_idle:5G,not_restricted_rrc_idle:5G," + "not_restricted_rrc_con:5G"); sDefaults.putString(KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, ""); sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, ""); @@ -10830,6 +10880,7 @@ public class CarrierConfigManager { new boolean[] {false, false, true, false, false}); sDefaults.putStringArray(KEY_CARRIER_SERVICE_NAME_STRING_ARRAY, new String[0]); sDefaults.putStringArray(KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY, new String[0]); + sDefaults.putIntArray(KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY, new int[]{1, 2, 3}); } /** diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java new file mode 100644 index 000000000000..61d7ead67a21 --- /dev/null +++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java @@ -0,0 +1,214 @@ +/* + * 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.telephony; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * A single occurrence capturing a notable change to previously reported + * cryptography algorithms for a given network and network event. + * + * @hide + */ +public final class SecurityAlgorithmUpdate implements Parcelable { + private static final String TAG = "SecurityAlgorithmUpdate"; + + private @ConnectionEvent int mConnectionEvent; + private @SecurityAlgorithm int mEncryption; + private @SecurityAlgorithm int mIntegrity; + private boolean mIsUnprotectedEmergency; + + public SecurityAlgorithmUpdate(@ConnectionEvent int connectionEvent, + @SecurityAlgorithm int encryption, @SecurityAlgorithm int integrity, + boolean isUnprotectedEmergency) { + mConnectionEvent = connectionEvent; + mEncryption = encryption; + mIntegrity = integrity; + mIsUnprotectedEmergency = isUnprotectedEmergency; + } + + private SecurityAlgorithmUpdate(Parcel in) { + readFromParcel(in); + } + + public @ConnectionEvent int getConnectionEvent() { + return mConnectionEvent; + } + + public @SecurityAlgorithm int getEncryption() { + return mEncryption; + } + + public @SecurityAlgorithm int getIntegrity() { + return mIntegrity; + } + + public boolean isUnprotectedEmergency() { + return mIsUnprotectedEmergency; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mConnectionEvent); + out.writeInt(mEncryption); + out.writeInt(mIntegrity); + out.writeBoolean(mIsUnprotectedEmergency); + } + + private void readFromParcel(@NonNull Parcel in) { + mConnectionEvent = in.readInt(); + mEncryption = in.readInt(); + mIntegrity = in.readInt(); + mIsUnprotectedEmergency = in.readBoolean(); + } + + public static final Parcelable.Creator<SecurityAlgorithmUpdate> CREATOR = + new Parcelable.Creator<SecurityAlgorithmUpdate>() { + public SecurityAlgorithmUpdate createFromParcel(Parcel in) { + return new SecurityAlgorithmUpdate(in); + } + + public SecurityAlgorithmUpdate[] newArray(int size) { + return new SecurityAlgorithmUpdate[size]; + } + }; + + @Override + public String toString() { + return TAG + ":{ mConnectionEvent = " + mConnectionEvent + " mEncryption = " + mEncryption + + " mIntegrity = " + mIntegrity + " mIsUnprotectedEmergency = " + + mIsUnprotectedEmergency; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SecurityAlgorithmUpdate)) return false; + SecurityAlgorithmUpdate that = (SecurityAlgorithmUpdate) o; + return mConnectionEvent == that.mConnectionEvent + && mEncryption == that.mEncryption + && mIntegrity == that.mIntegrity + && mIsUnprotectedEmergency == that.mIsUnprotectedEmergency; + } + + @Override + public int hashCode() { + return Objects.hash(mConnectionEvent, mEncryption, mIntegrity, mIsUnprotectedEmergency); + } + + public static final int CONNECTION_EVENT_CS_SIGNALLING_GSM = 0; + public static final int CONNECTION_EVENT_PS_SIGNALLING_GPRS = 1; + public static final int CONNECTION_EVENT_CS_SIGNALLING_3G = 2; + public static final int CONNECTION_EVENT_PS_SIGNALLING_3G = 3; + public static final int CONNECTION_EVENT_NAS_SIGNALLING_LTE = 4; + public static final int CONNECTION_EVENT_AS_SIGNALLING_LTE = 5; + public static final int CONNECTION_EVENT_VOLTE_SIP = 6; + public static final int CONNECTION_EVENT_VOLTE_RTP = 7; + public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 8; + public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 9; + public static final int CONNECTION_EVENT_VONR_SIP = 10; + public static final int CONNECTION_EVENT_VONR_RTP = 11; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"CONNECTION_EVENT_"}, value = {CONNECTION_EVENT_CS_SIGNALLING_GSM, + CONNECTION_EVENT_PS_SIGNALLING_GPRS, CONNECTION_EVENT_CS_SIGNALLING_3G, + CONNECTION_EVENT_PS_SIGNALLING_3G, CONNECTION_EVENT_NAS_SIGNALLING_LTE, + CONNECTION_EVENT_AS_SIGNALLING_LTE, CONNECTION_EVENT_VOLTE_SIP, + CONNECTION_EVENT_VOLTE_RTP, CONNECTION_EVENT_NAS_SIGNALLING_5G, + CONNECTION_EVENT_AS_SIGNALLING_5G, CONNECTION_EVENT_VONR_SIP, + CONNECTION_EVENT_VONR_RTP}) + public @interface ConnectionEvent { + } + + public static final int SECURITY_ALGORITHM_A50 = 0; + public static final int SECURITY_ALGORITHM_A51 = 1; + public static final int SECURITY_ALGORITHM_A52 = 2; + public static final int SECURITY_ALGORITHM_A53 = 3; + public static final int SECURITY_ALGORITHM_A54 = 4; + public static final int SECURITY_ALGORITHM_GEA0 = 14; + public static final int SECURITY_ALGORITHM_GEA1 = 15; + public static final int SECURITY_ALGORITHM_GEA2 = 16; + public static final int SECURITY_ALGORITHM_GEA3 = 17; + public static final int SECURITY_ALGORITHM_GEA4 = 18; + public static final int SECURITY_ALGORITHM_GEA5 = 19; + public static final int SECURITY_ALGORITHM_UEA0 = 29; + public static final int SECURITY_ALGORITHM_UEA1 = 30; + public static final int SECURITY_ALGORITHM_UEA2 = 31; + public static final int SECURITY_ALGORITHM_EEA0 = 41; + public static final int SECURITY_ALGORITHM_EEA1 = 42; + public static final int SECURITY_ALGORITHM_EEA2 = 43; + public static final int SECURITY_ALGORITHM_EEA3 = 44; + public static final int SECURITY_ALGORITHM_NEA0 = 55; + public static final int SECURITY_ALGORITHM_NEA1 = 56; + public static final int SECURITY_ALGORITHM_NEA2 = 57; + public static final int SECURITY_ALGORITHM_NEA3 = 58; + public static final int SECURITY_ALGORITHM_SIP_NULL = 68; + public static final int SECURITY_ALGORITHM_AES_GCM = 69; + public static final int SECURITY_ALGORITHM_AES_GMAC = 70; + public static final int SECURITY_ALGORITHM_AES_CBC = 71; + public static final int SECURITY_ALGORITHM_DES_EDE3_CBC = 72; + public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73; + public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74; + public static final int SECURITY_ALGORITHM_HMAC_SHA1_96_NULL = 75; + public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 76; + public static final int SECURITY_ALGORITHM_HMAC_MD5_96_NULL = 77; + public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87; + public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88; + public static final int SECURITY_ALGORITHM_SRTP_HMAC_SHA1 = 89; + public static final int SECURITY_ALGORITHM_ENCR_AES_GCM_16 = 99; + public static final int SECURITY_ALGORITHM_ENCR_AES_CBC = 100; + public static final int SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128 = 101; + public static final int SECURITY_ALGORITHM_UNKNOWN = 113; + public static final int SECURITY_ALGORITHM_OTHER = 114; + public static final int SECURITY_ALGORITHM_ORYX = 124; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"CONNECTION_EVENT_"}, value = {SECURITY_ALGORITHM_A50, SECURITY_ALGORITHM_A51, + SECURITY_ALGORITHM_A52, SECURITY_ALGORITHM_A53, + SECURITY_ALGORITHM_A54, SECURITY_ALGORITHM_GEA0, SECURITY_ALGORITHM_GEA1, + SECURITY_ALGORITHM_GEA2, SECURITY_ALGORITHM_GEA3, SECURITY_ALGORITHM_GEA4, + SECURITY_ALGORITHM_GEA5, SECURITY_ALGORITHM_UEA0, SECURITY_ALGORITHM_UEA1, + SECURITY_ALGORITHM_UEA2, SECURITY_ALGORITHM_EEA0, SECURITY_ALGORITHM_EEA1, + SECURITY_ALGORITHM_EEA2, SECURITY_ALGORITHM_EEA3, SECURITY_ALGORITHM_NEA0, + SECURITY_ALGORITHM_NEA1, SECURITY_ALGORITHM_NEA2, SECURITY_ALGORITHM_NEA3, + SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM, + SECURITY_ALGORITHM_AES_GMAC, SECURITY_ALGORITHM_AES_CBC, + SECURITY_ALGORITHM_DES_EDE3_CBC, SECURITY_ALGORITHM_AES_EDE3_CBC, + SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_SHA1_96_NULL, + SECURITY_ALGORITHM_HMAC_MD5_96, SECURITY_ALGORITHM_HMAC_MD5_96_NULL, + SECURITY_ALGORITHM_SRTP_AES_COUNTER, SECURITY_ALGORITHM_SRTP_AES_F8, + SECURITY_ALGORITHM_SRTP_HMAC_SHA1, SECURITY_ALGORITHM_ENCR_AES_GCM_16, + SECURITY_ALGORITHM_ENCR_AES_CBC, SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128, + SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX}) + public @interface SecurityAlgorithm { + } + +} diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 6ebf3bea7b6d..a188581ef695 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -54,6 +54,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; /** * A Parcelable class for Subscription Information. @@ -262,6 +263,11 @@ public class SubscriptionInfo implements Parcelable { private final boolean mIsOnlyNonTerrestrialNetwork; /** + * The service capabilities (in the form of bitmask combination) the subscription supports. + */ + private final int mServiceCapabilities; + + /** * @hide * * @deprecated Use {@link SubscriptionInfo.Builder}. @@ -386,6 +392,7 @@ public class SubscriptionInfo implements Parcelable { this.mPortIndex = portIndex; this.mUsageSetting = usageSetting; this.mIsOnlyNonTerrestrialNetwork = false; + this.mServiceCapabilities = 0; } /** @@ -425,6 +432,7 @@ public class SubscriptionInfo implements Parcelable { this.mPortIndex = builder.mPortIndex; this.mUsageSetting = builder.mUsageSetting; this.mIsOnlyNonTerrestrialNetwork = builder.mIsOnlyNonTerrestrialNetwork; + this.mServiceCapabilities = builder.mServiceCapabilities; } /** @@ -882,6 +890,44 @@ public class SubscriptionInfo implements Parcelable { return mIsOnlyNonTerrestrialNetwork; } + // TODO(b/316183370): replace @code with @link in javadoc after feature is released + /** + * Retrieves the service capabilities for the current subscription. + * + * <p>These capabilities are hint to system components and applications, allowing them to + * enhance user experience. For instance, a Dialer application can inform the user that the + * current subscription is incapable of making voice calls if the voice service is not + * available. + * + * <p>Correct usage of these service capabilities must also consider the device's overall + * service capabilities. For example, even if the subscription supports voice calls, a voice + * call might not be feasible on a device that only supports data services. To determine the + * device's capabilities for voice and SMS services, refer to + * {@code TelephonyManager#isDeviceVoiceCapable()} and + * {@code TelephonyManager#isDeviceSmsCapable()}. + * + * <p>Emergency service availability may not directly correlate with the subscription or + * device's general service capabilities. In some cases, emergency calls might be possible + * even if the subscription or device does not typically support voice services. + * + * @return A set of integer representing the subscription's service capabilities, + * defined by {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE}, + * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS} + * and {@code SubscriptionManager#SERVICE_CAPABILITY_DATA}. + * + * @see TelephonyManager#isDeviceVoiceCapable() + * @see TelephonyManager#isDeviceSmsCapable() + * @see CarrierConfigManager#KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY + * @see SubscriptionManager#SERVICE_CAPABILITY_VOICE + * @see SubscriptionManager#SERVICE_CAPABILITY_SMS + * @see SubscriptionManager#SERVICE_CAPABILITY_DATA + */ + @NonNull + @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) + public @SubscriptionManager.ServiceCapability Set<Integer> getServiceCapabilities() { + return SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities); + } + @NonNull public static final Parcelable.Creator<SubscriptionInfo> CREATOR = new Parcelable.Creator<SubscriptionInfo>() { @@ -919,6 +965,8 @@ public class SubscriptionInfo implements Parcelable { .setUiccApplicationsEnabled(source.readBoolean()) .setUsageSetting(source.readInt()) .setOnlyNonTerrestrialNetwork(source.readBoolean()) + .setServiceCapabilities( + SubscriptionManager.getServiceCapabilitiesSet(source.readInt())) .build(); } @@ -961,6 +1009,7 @@ public class SubscriptionInfo implements Parcelable { dest.writeBoolean(mAreUiccApplicationsEnabled); dest.writeInt(mUsageSetting); dest.writeBoolean(mIsOnlyNonTerrestrialNetwork); + dest.writeInt(mServiceCapabilities); } @Override @@ -1024,6 +1073,8 @@ public class SubscriptionInfo implements Parcelable { + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled + " usageSetting=" + SubscriptionManager.usageSettingToString(mUsageSetting) + " isOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork + + " serviceCapabilities=" + SubscriptionManager.getServiceCapabilitiesSet( + mServiceCapabilities).toString() + "]"; } @@ -1049,7 +1100,8 @@ public class SubscriptionInfo implements Parcelable { that.mNativeAccessRules) && Arrays.equals(mCarrierConfigAccessRules, that.mCarrierConfigAccessRules) && Objects.equals(mGroupUuid, that.mGroupUuid) && mCountryIso.equals(that.mCountryIso) && mGroupOwner.equals(that.mGroupOwner) - && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork; + && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork + && mServiceCapabilities == that.mServiceCapabilities; } @Override @@ -1058,7 +1110,7 @@ public class SubscriptionInfo implements Parcelable { mDisplayNameSource, mIconTint, mNumber, mDataRoaming, mMcc, mMnc, mIsEmbedded, mCardString, mIsOpportunistic, mGroupUuid, mCountryIso, mCarrierId, mProfileClass, mType, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting, mCardId, - mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork); + mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities); result = 31 * result + Arrays.hashCode(mEhplmns); result = 31 * result + Arrays.hashCode(mHplmns); result = 31 * result + Arrays.hashCode(mNativeAccessRules); @@ -1263,6 +1315,11 @@ public class SubscriptionInfo implements Parcelable { private boolean mIsOnlyNonTerrestrialNetwork = false; /** + * Service capabilities bitmasks the subscription supports. + */ + private int mServiceCapabilities = 0; + + /** * Default constructor. */ public Builder() { @@ -1305,6 +1362,7 @@ public class SubscriptionInfo implements Parcelable { mPortIndex = info.mPortIndex; mUsageSetting = info.mUsageSetting; mIsOnlyNonTerrestrialNetwork = info.mIsOnlyNonTerrestrialNetwork; + mServiceCapabilities = info.mServiceCapabilities; } /** @@ -1703,6 +1761,32 @@ public class SubscriptionInfo implements Parcelable { } /** + * Set the service capabilities that the subscription supports. + * + * @param capabilities Bitmask combination of SubscriptionManager + * .SERVICE_CAPABILITY_XXX. + * @return The builder. + * + * @throws IllegalArgumentException when any capability is not supported. + */ + @NonNull + @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) + public Builder setServiceCapabilities( + @NonNull @SubscriptionManager.ServiceCapability Set<Integer> capabilities) { + int combinedCapabilities = 0; + for (int capability : capabilities) { + if (capability < SubscriptionManager.SERVICE_CAPABILITY_VOICE + || capability > SubscriptionManager.SERVICE_CAPABILITY_MAX) { + throw new IllegalArgumentException( + "Invalid service capability value: " + capability); + } + combinedCapabilities |= SubscriptionManager.serviceCapabilityToBitmask(capability); + } + mServiceCapabilities = combinedCapabilities; + return this; + } + + /** * Build the {@link SubscriptionInfo}. * * @return The {@link SubscriptionInfo} instance. diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 326b6f5af613..6c8663a8eb14 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -88,10 +88,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -1138,6 +1140,14 @@ public class SubscriptionManager { */ public static final String IS_NTN = SimInfo.COLUMN_IS_NTN; + /** + * TelephonyProvider column name to identify service capabilities. + * Disabled by default. + * <P>Type: INTEGER (int)</P> + * @hide + */ + public static final String SERVICE_CAPABILITIES = SimInfo.COLUMN_SERVICE_CAPABILITIES; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"USAGE_SETTING_"}, @@ -1347,6 +1357,86 @@ public class SubscriptionManager { }) public @interface PhoneNumberSource {} + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SERVICE_CAPABILITY"}, + value = { + SERVICE_CAPABILITY_VOICE, + SERVICE_CAPABILITY_SMS, + SERVICE_CAPABILITY_DATA, + }) + public @interface ServiceCapability { + } + + /** + * Represents a value indicating the voice calling capabilities of a subscription. + * + * <p>This value identifies whether the subscription supports various voice calling services. + * These services can include circuit-switched (CS) calling, packet-switched (PS) IMS (IP + * Multimedia Subsystem) calling, and over-the-top (OTT) calling options. + * + * <p>Note: The availability of emergency calling services is not solely dependent on this + * voice capability. Emergency services may be accessible even if the subscription lacks + * standard voice capabilities. However, the device's ability to support emergency calls + * can be influenced by its inherent voice capabilities, as determined by + * {@link TelephonyManager#isDeviceVoiceCapable()}. + * + * @see TelephonyManager#isDeviceVoiceCapable() + */ + @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) + public static final int SERVICE_CAPABILITY_VOICE = 1; + + /** + * Represents a value indicating the SMS capabilities of a subscription. + * + * <p>This value identifies whether the subscription supports various sms services. + * These services can include circuit-switched (CS) SMS, packet-switched (PS) IMS (IP + * Multimedia Subsystem) SMS, and over-the-top (OTT) SMS options. + * + * <p>Note: The availability of emergency SMS services is not solely dependent on this + * sms capability. Emergency services may be accessible even if the subscription lacks + * standard sms capabilities. However, the device's ability to support emergency sms + * can be influenced by its inherent sms capabilities, as determined by + * {@link TelephonyManager#isDeviceSmsCapable()}. + * + * @see TelephonyManager#isDeviceSmsCapable() + */ + @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) + public static final int SERVICE_CAPABILITY_SMS = 2; + + /** + * Represents a value indicating the data calling capabilities of a subscription. + */ + @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) + public static final int SERVICE_CAPABILITY_DATA = 3; + + /** + * Maximum value of service capabilities supported so far. + * @hide + */ + public static final int SERVICE_CAPABILITY_MAX = SERVICE_CAPABILITY_DATA; + + /** + * Bitmask for {@code SERVICE_CAPABILITY_VOICE}. + * @hide + */ + public static final int SERVICE_CAPABILITY_VOICE_BITMASK = + serviceCapabilityToBitmask(SERVICE_CAPABILITY_VOICE); + + /** + * Bitmask for {@code SERVICE_CAPABILITY_SMS}. + * @hide + */ + public static final int SERVICE_CAPABILITY_SMS_BITMASK = + serviceCapabilityToBitmask(SERVICE_CAPABILITY_SMS); + + /** + * Bitmask for {@code SERVICE_CAPABILITY_DATA}. + * @hide + */ + public static final int SERVICE_CAPABILITY_DATA_BITMASK = + serviceCapabilityToBitmask(SERVICE_CAPABILITY_DATA); + private final Context mContext; /** @@ -4484,4 +4574,38 @@ public class SubscriptionManager { } return new ArrayList<>(); } + + /** + * @return the bitmasks combination of all service capabilities. + * @hide + */ + public static int getAllServiceCapabilityBitmasks() { + return SERVICE_CAPABILITY_VOICE_BITMASK | SERVICE_CAPABILITY_SMS_BITMASK + | SERVICE_CAPABILITY_DATA_BITMASK; + } + + /** + * @return The set of service capability from a bitmask combined one. + * @hide + */ + @NonNull + @ServiceCapability + public static Set<Integer> getServiceCapabilitiesSet(int combinedServiceCapabilities) { + Set<Integer> capabilities = new HashSet<>(); + for (int i = SERVICE_CAPABILITY_VOICE; i <= SERVICE_CAPABILITY_MAX; i++) { + final int capabilityBitmask = serviceCapabilityToBitmask(i); + if ((combinedServiceCapabilities & capabilityBitmask) == capabilityBitmask) { + capabilities.add(i); + } + } + return Collections.unmodifiableSet(capabilities); + } + + /** + * @return The service capability bitmask from a {@link ServiceCapability} value. + * @hide + */ + public static int serviceCapabilityToBitmask(@ServiceCapability int capability) { + return 1 << (capability - 1); + } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f8166e58cd2f..22a5cd7a478a 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -6683,8 +6683,10 @@ public class TelephonyManager { * PackageManager.FEATURE_TELEPHONY system feature, which is available * on any device with a telephony radio, even if the device is * data-only. + * @deprecated Replaced by {@link #isDeviceVoiceCapable()} */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING) + @Deprecated public boolean isVoiceCapable() { if (mContext == null) return true; return mContext.getResources().getBoolean( @@ -6692,6 +6694,30 @@ public class TelephonyManager { } /** + * @return true if the current device is "voice capable". + * <p> + * "Voice capable" means that this device supports circuit-switched or IMS packet switched + * (i.e. voice) phone calls over the telephony network, and is allowed to display the in-call + * UI while a cellular voice call is active. This will be false on "data only" devices which + * can't make voice calls and don't support any in-call UI. + * <p> + * Note: the meaning of this flag is subtly different from the PackageManager + * .FEATURE_TELEPHONY system feature, which is available on any device with a telephony + * radio, even if the device is data-only. + * <p> + * To check if a subscription is "voice capable", call method + * {@link SubscriptionInfo#getServiceCapabilities()} and compare the result with + * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE}. + * + * @see SubscriptionInfo#getServiceCapabilities() + */ + @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING) + @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) + public boolean isDeviceVoiceCapable() { + return isVoiceCapable(); + } + + /** * @return true if the current device supports sms service. * <p> * If true, this means that the device supports both sending and @@ -6699,6 +6725,7 @@ public class TelephonyManager { * <p> * Note: Voicemail waiting sms, cell broadcasting sms, and MMS are * disabled when device doesn't support sms. + * @deprecated Replaced by {@link #isDeviceSmsCapable()} */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING) public boolean isSmsCapable() { @@ -6708,6 +6735,27 @@ public class TelephonyManager { } /** + * @return true if the current device supports SMS service. + * <p> + * If true, this means that the device supports both sending and + * receiving SMS via the telephony network. + * <p> + * Note: Voicemail waiting SMS, cell broadcasting SMS, and MMS are + * disabled when device doesn't support SMS. + * <p> + * To check if a subscription is "SMS capable", call method + * {@link SubscriptionInfo#getServiceCapabilities()} and compare result with + * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_SMS}. + * + * @see SubscriptionInfo#getServiceCapabilities() + */ + @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING) + @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE) + public boolean isDeviceSmsCapable() { + return isSmsCapable(); + } + + /** * Requests all available cell information from all radios on the device including the * camped/registered, serving, and neighboring cells. * diff --git a/tests/ActivityManagerPerfTests/tests/Android.bp b/tests/ActivityManagerPerfTests/tests/Android.bp index e5813aec9f43..cce40f3a2664 100644 --- a/tests/ActivityManagerPerfTests/tests/Android.bp +++ b/tests/ActivityManagerPerfTests/tests/Android.bp @@ -30,6 +30,12 @@ android_test { "ActivityManagerPerfTestsUtils", "collector-device-lib-platform", ], + data: [ + ":ActivityManagerPerfTestsTestApp", + ":ActivityManagerPerfTestsStubApp1", + ":ActivityManagerPerfTestsStubApp2", + ":ActivityManagerPerfTestsStubApp3", + ], platform_apis: true, min_sdk_version: "25", // For android.permission.FORCE_STOP_PACKAGES permission diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt index c49f8fecfdee..be47ec7098f9 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt @@ -19,12 +19,12 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation +import android.tools.common.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.traces.parsers.toFlickerComponent -import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper @@ -58,9 +58,10 @@ class CloseImeToHomeOnFinishActivityTest(flicker: LegacyFlickerTest) : BaseTest( } transitions { broadcastActionTrigger.doAction(ACTION_FINISH_ACTIVITY) - wmHelper.StateSyncBuilder() - .withActivityRemoved(ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent()) - .waitForAndVerify() + wmHelper + .StateSyncBuilder() + .withActivityRemoved(ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent()) + .waitForAndVerify() } teardown { simpleApp.exit(wmHelper) } } @@ -69,10 +70,16 @@ class CloseImeToHomeOnFinishActivityTest(flicker: LegacyFlickerTest) : BaseTest( @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible() - @FlakyTest(bugId = 246284124) + @Presubmit @Test override fun visibleLayersShownMoreThanOneConsecutiveEntry() { - super.visibleLayersShownMoreThanOneConsecutiveEntry() + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS.toMutableList().also { + it.add(simpleApp.componentMatcher) + } + ) + } } @Presubmit diff --git a/tests/testables/src/android/testing/TestWithLooperRule.java b/tests/testables/src/android/testing/TestWithLooperRule.java index 99b303e0c43a..37b39c314e53 100644 --- a/tests/testables/src/android/testing/TestWithLooperRule.java +++ b/tests/testables/src/android/testing/TestWithLooperRule.java @@ -19,7 +19,6 @@ package android.testing; import android.testing.TestableLooper.LooperFrameworkMethod; import android.testing.TestableLooper.RunWithLooper; -import org.junit.internal.runners.statements.InvokeMethod; import org.junit.rules.MethodRule; import org.junit.runner.RunWith; import org.junit.runners.model.FrameworkMethod; @@ -79,13 +78,11 @@ public class TestWithLooperRule implements MethodRule { while (next != null) { switch (next.getClass().getSimpleName()) { case "RunAfters": - this.<List<FrameworkMethod>>wrapFieldMethodFor(next, - next.getClass(), "afters", method, target); + this.wrapFieldMethodFor(next, "afters", method, target); next = getNextStatement(next, "next"); break; case "RunBefores": - this.<List<FrameworkMethod>>wrapFieldMethodFor(next, - next.getClass(), "befores", method, target); + this.wrapFieldMethodFor(next, "befores", method, target); next = getNextStatement(next, "next"); break; case "FailOnTimeout": @@ -95,8 +92,10 @@ public class TestWithLooperRule implements MethodRule { next = getNextStatement(next, "originalStatement"); break; case "InvokeMethod": - this.<FrameworkMethod>wrapFieldMethodFor(next, - InvokeMethod.class, "testMethod", method, target); + this.wrapFieldMethodFor(next, "testMethod", method, target); + return; + case "InvokeParameterizedMethod": + this.wrapFieldMethodFor(next, "frameworkMethod", method, target); return; default: throw new Exception( @@ -112,12 +111,11 @@ public class TestWithLooperRule implements MethodRule { // Wrapping the befores, afters, and InvokeMethods with LooperFrameworkMethod // within the statement. - private <T> void wrapFieldMethodFor(Statement base, Class<?> targetClass, String fieldStr, - FrameworkMethod method, Object target) - throws NoSuchFieldException, IllegalAccessException { - Field field = targetClass.getDeclaredField(fieldStr); + private void wrapFieldMethodFor(Statement base, String fieldStr, FrameworkMethod method, + Object target) throws NoSuchFieldException, IllegalAccessException { + Field field = base.getClass().getDeclaredField(fieldStr); field.setAccessible(true); - T fieldInstance = (T) field.get(base); + Object fieldInstance = field.get(base); if (fieldInstance instanceof FrameworkMethod) { field.set(base, looperWrap(method, target, (FrameworkMethod) fieldInstance)); } else { diff --git a/tools/hoststubgen/README.md b/tools/hoststubgen/README.md index 3455b0af4360..1a895dc7dfce 100644 --- a/tools/hoststubgen/README.md +++ b/tools/hoststubgen/README.md @@ -34,11 +34,6 @@ AndroidHeuristicsFilter has hardcoded heuristics to detect AIDL generated classe - `test-tiny-framework/` See `README.md` in it. - - `test-framework` - This directory was used during the prototype phase, but now that we have real ravenwood tests, - this directory is obsolete and should be deleted. - - - `scripts` - `dump-jar.sh` diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index 4eac361d6e53..57bcc04a8aec 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -9,7 +9,7 @@ package { // Visibility only for ravenwood prototype uses. genrule_defaults { - name: "hoststubgen-for-prototype-only-genrule", + name: "ravenwood-internal-only-visibility-genrule", visibility: [ ":__subpackages__", "//frameworks/base", @@ -19,7 +19,7 @@ genrule_defaults { // Visibility only for ravenwood prototype uses. java_defaults { - name: "hoststubgen-for-prototype-only-java", + name: "ravenwood-internal-only-visibility-java", visibility: [ ":__subpackages__", "//frameworks/base", @@ -29,7 +29,7 @@ java_defaults { // Visibility only for ravenwood prototype uses. filegroup_defaults { - name: "hoststubgen-for-prototype-only-filegroup", + name: "ravenwood-internal-only-visibility-filegroup", visibility: [ ":__subpackages__", "//frameworks/base", @@ -41,7 +41,7 @@ filegroup_defaults { // This is only for the prototype. The productionized version is "ravenwood-annotations". java_library { name: "hoststubgen-annotations", - defaults: ["hoststubgen-for-prototype-only-java"], + defaults: ["ravenwood-internal-only-visibility-java"], srcs: [ "annotations-src/**/*.java", ], @@ -115,7 +115,7 @@ java_test_host { // This is only for the prototype. The productionized version is "ravenwood-standard-options". filegroup { name: "hoststubgen-standard-options", - defaults: ["hoststubgen-for-prototype-only-filegroup"], + defaults: ["ravenwood-internal-only-visibility-filegroup"], srcs: [ "hoststubgen-standard-options.txt", ], @@ -153,149 +153,25 @@ genrule_defaults { ], } -// Generate the stub/impl from framework-all, with hidden APIs. -java_genrule_host { - name: "framework-all-hidden-api-host", - defaults: ["hoststubgen-command-defaults"], - cmd: hoststubgen_common_options + - "--in-jar $(location :framework-all) " + - "--policy-override-file $(location framework-policy-override.txt) ", - srcs: [ - ":framework-all", - "framework-policy-override.txt", - ], - visibility: ["//visibility:private"], -} - -// Extract the stub jar from "framework-all-host" for subsequent build rules. -// This is only for the prototype. Do not use it in "productionized" build rules. -java_genrule_host { - name: "framework-all-hidden-api-host-stub", - defaults: ["hoststubgen-for-prototype-only-genrule"], - cmd: "cp $(in) $(out)", - srcs: [ - ":framework-all-hidden-api-host{host_stub.jar}", - ], - out: [ - "host_stub.jar", - ], -} - -// Extract the impl jar from "framework-all-host" for subsequent build rules. -// This is only for the prototype. Do not use it in "productionized" build rules. -java_genrule_host { - name: "framework-all-hidden-api-host-impl", - defaults: ["hoststubgen-for-prototype-only-genrule"], - cmd: "cp $(in) $(out)", - srcs: [ - ":framework-all-hidden-api-host{host_impl.jar}", - ], - out: [ - "host_impl.jar", - ], -} - -// Generate the stub/impl from framework-all, with only public/system/test APIs, without -// hidden APIs. -// This is only for the prototype. Do not use it in "productionized" build rules. -java_genrule_host { - name: "framework-all-test-api-host", - defaults: ["hoststubgen-command-defaults"], - cmd: hoststubgen_common_options + - "--intersect-stub-jar $(location :android_test_stubs_current{.jar}) " + - "--in-jar $(location :framework-all) " + - "--policy-override-file $(location framework-policy-override.txt) ", - srcs: [ - ":framework-all", - ":android_test_stubs_current{.jar}", - "framework-policy-override.txt", - ], - visibility: ["//visibility:private"], -} - -// Extract the stub jar from "framework-all-test-api-host" for subsequent build rules. -// This is only for the prototype. Do not use it in "productionized" build rules. -java_genrule_host { - name: "framework-all-test-api-host-stub", - defaults: ["hoststubgen-for-prototype-only-genrule"], - cmd: "cp $(in) $(out)", - srcs: [ - ":framework-all-test-api-host{host_stub.jar}", - ], - out: [ - "host_stub.jar", - ], -} - -// Extract the impl jar from "framework-all-test-api-host" for subsequent build rules. -// This is only for the prototype. Do not use it in "productionized" build rules. -java_genrule_host { - name: "framework-all-test-api-host-impl", - defaults: ["hoststubgen-for-prototype-only-genrule"], - cmd: "cp $(in) $(out)", - srcs: [ - ":framework-all-test-api-host{host_impl.jar}", - ], - out: [ - "host_impl.jar", - ], -} - -// This library contains helper classes to build hostside tests/targets. -// This essentially contains dependencies from tests that we can't actually use the real ones. -// For example, the actual AndroidTestCase and AndroidJUnit4 don't run on the host side (yet), -// so we pup "fake" implementations here. -// Ideally this library should be empty. -java_library_host { - name: "hoststubgen-helper-framework-buildtime", - defaults: ["hoststubgen-for-prototype-only-java"], - srcs: [ - "helper-framework-buildtime-src/**/*.java", - ], - libs: [ - // We need it to pull in some of the framework classes used in this library, - // such as Context.java. - "framework-all-hidden-api-host-impl", - "junit", - ], -} - -// This module contains "fake" libcore/dalvik classes, framework native substitution, etc, -// that are needed at runtime. -java_library_host { - name: "hoststubgen-helper-framework-runtime", - defaults: ["hoststubgen-for-prototype-only-java"], - srcs: [ - "helper-framework-runtime-src/**/*.java", - ], - exclude_srcs: [ - "helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java", - ], - libs: [ - "hoststubgen-helper-runtime", - "framework-all-hidden-api-host-impl", - ], -} - java_library_host { name: "hoststubgen-helper-libcore-runtime", - defaults: ["hoststubgen-for-prototype-only-java"], srcs: [ "helper-framework-runtime-src/libcore-fake/**/*.java", ], + visibility: ["//visibility:private"], } java_host_for_device { name: "hoststubgen-helper-libcore-runtime.ravenwood", - defaults: ["hoststubgen-for-prototype-only-java"], libs: [ "hoststubgen-helper-libcore-runtime", ], + visibility: ["//visibility:private"], } java_library { name: "hoststubgen-helper-framework-runtime.ravenwood", - defaults: ["hoststubgen-for-prototype-only-java"], + defaults: ["ravenwood-internal-only-visibility-java"], srcs: [ "helper-framework-runtime-src/framework/**/*.java", ], @@ -308,88 +184,3 @@ java_library { "hoststubgen-helper-libcore-runtime.ravenwood", ], } - -// Defaults for host side test modules. -// We need two rules for each test. -// 1. A "-test-lib" jar, which compiles the test against the stub jar. -// This one is only used by the second rule, so it should be "private. -// 2. A "-test" jar, which includes 1 + the runtime (impl) jars. - -// This and next ones are for tests using framework-app, with hidden APIs. -java_defaults { - name: "hosttest-with-framework-all-hidden-api-test-lib-defaults", - installable: false, - libs: [ - "framework-all-hidden-api-host-stub", - ], - static_libs: [ - "hoststubgen-helper-framework-buildtime", - "framework-annotations-lib", - ], - visibility: ["//visibility:private"], -} - -// Default rules to include `libandroid_runtime`. For now, it's empty, but we'll use it -// once we start using JNI. -java_defaults { - name: "hosttest-with-libandroid_runtime", - jni_libs: [ - // "libandroid_runtime", - - // TODO: Figure out how to build them automatically. - // Following ones are depended by libandroid_runtime. - // Without listing them here, not only we won't get them under - // $ANDROID_HOST_OUT/testcases/*/lib64, but also not under - // $ANDROID_HOST_OUT/lib64, so we'd fail to load them at runtime. - // ($ANDROID_HOST_OUT/lib/ gets all of them though.) - // "libcutils", - // "libharfbuzz_ng", - // "libminikin", - // "libz", - // "libbinder", - // "libhidlbase", - // "libvintf", - // "libicu", - // "libutils", - // "libtinyxml2", - ], -} - -java_defaults { - name: "hosttest-with-framework-all-hidden-api-test-defaults", - defaults: ["hosttest-with-libandroid_runtime"], - installable: false, - test_config: "AndroidTest-host.xml", - static_libs: [ - "hoststubgen-helper-runtime", - "hoststubgen-helper-framework-runtime", - "framework-all-hidden-api-host-impl", - ], -} - -// This and next ones are for tests using framework-app, with public/system/test APIs, -// without hidden APIs. -java_defaults { - name: "hosttest-with-framework-all-test-api-test-lib-defaults", - installable: false, - libs: [ - "framework-all-test-api-host-stub", - ], - static_libs: [ - "hoststubgen-helper-framework-buildtime", - "framework-annotations-lib", - ], - visibility: ["//visibility:private"], -} - -java_defaults { - name: "hosttest-with-framework-all-test-api-test-defaults", - defaults: ["hosttest-with-libandroid_runtime"], - installable: false, - test_config: "AndroidTest-host.xml", - static_libs: [ - "hoststubgen-helper-runtime", - "hoststubgen-helper-framework-runtime", - "framework-all-test-api-host-impl", - ], -} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java deleted file mode 100644 index 51c5d9a05e52..000000000000 --- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2013 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 androidx.annotation; - -// [ravenwood] TODO: Find the actual androidx jar containing it.s - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * Denotes that a parameter, field or method return value can never be null. - * <p> - * This is a marker annotation and it has no specific attributes. - * - * @paramDoc This value cannot be {@code null}. - * @returnDoc This value cannot be {@code null}. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, PARAMETER, FIELD}) -public @interface NonNull { -} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java deleted file mode 100644 index f1f0e8b43f16..000000000000 --- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2013 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 androidx.annotation; - -// [ravenwood] TODO: Find the actual androidx jar containing it.s - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * Denotes that a parameter, field or method return value can be null. - * <p> - * When decorating a method call parameter, this denotes that the parameter can - * legitimately be null and the method will gracefully deal with it. Typically - * used on optional parameters. - * <p> - * When decorating a method, this denotes the method might legitimately return - * null. - * <p> - * This is a marker annotation and it has no specific attributes. - * - * @paramDoc This value may be {@code null}. - * @returnDoc This value may be {@code null}. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, PARAMETER, FIELD}) -public @interface Nullable { -} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java deleted file mode 100644 index 0c82e4e268d3..000000000000 --- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java +++ /dev/null @@ -1,30 +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 androidx.test.ext.junit.runners; - -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.model.InitializationError; - -// TODO: We need to simulate the androidx test runner. -// https://source.corp.google.com/piper///depot/google3/third_party/android/androidx_test/ext/junit/java/androidx/test/ext/junit/runners/AndroidJUnit4.java -// https://source.corp.google.com/piper///depot/google3/third_party/android/androidx_test/runner/android_junit_runner/java/androidx/test/internal/runner/junit4/AndroidJUnit4ClassRunner.java - -public class AndroidJUnit4 extends BlockJUnit4ClassRunner { - public AndroidJUnit4(Class<?> testClass) throws InitializationError { - super(testClass); - } -} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java deleted file mode 100644 index 2470d8390f5d..000000000000 --- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2014 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 androidx.test.filters; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Designates a test as being flaky (non-deterministic). - * - * <p>Can then be used to filter tests on execution using -e annotation or -e notAnnotation as - * desired. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.TYPE}) -public @interface FlakyTest { - /** - * An optional bug number associated with the test. -1 Means that no bug number is associated with - * the flaky annotation. - * - * @return int - */ - int bugId() default -1; - - /** - * Details, such as the reason of why the test is flaky. - * - * @return String - */ - String detail() default ""; -} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java deleted file mode 100644 index 578d7dc73647..000000000000 --- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2016 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 androidx.test.filters; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to assign a large test size qualifier to a test. This annotation can be used at a - * method or class level. - * - * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a - * test suite of similar run time. - * - * <p>Execution time: >1000ms - * - * <p>Large tests should be focused on testing integration of all application components. These - * tests fully participate in the system and may make use of all resources such as databases, file - * systems and network. As a rule of thumb most functional UI tests are large tests. - * - * <p>Note: This class replaces the deprecated Android platform size qualifier <a - * href="{@docRoot}reference/android/test/suitebuilder/annotation/LargeTest.html"><code> - * android.test.suitebuilder.annotation.LargeTest</code></a> and is the recommended way to annotate - * tests written with the AndroidX Test Library. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.TYPE}) -public @interface LargeTest {} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java deleted file mode 100644 index dfdaa53ee6ac..000000000000 --- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2016 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 androidx.test.filters; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to assign a medium test size qualifier to a test. This annotation can be used at a - * method or class level. - * - * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a - * test suite of similar run time. - * - * <p>Execution time: <1000ms - * - * <p>Medium tests should be focused on a very limited subset of components or a single component. - * Resource access to the file system through well defined interfaces like databases, - * ContentProviders, or Context is permitted. Network access should be restricted, (long-running) - * blocking operations should be avoided and use mock objects instead. - * - * <p>Note: This class replaces the deprecated Android platform size qualifier <a - * href="{@docRoot}reference/android/test/suitebuilder/annotation/MediumTest.html"><code> - * android.test.suitebuilder.annotation.MediumTest</code></a> and is the recommended way to annotate - * tests written with the AndroidX Test Library. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.TYPE}) -public @interface MediumTest {} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java deleted file mode 100644 index 3d3ee3318bfa..000000000000 --- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2014 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 androidx.test.filters; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Indicates that a specific test should not be run on emulator. - * - * <p>It will be executed only if the test is running on the physical android device. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE, ElementType.METHOD}) -public @interface RequiresDevice {} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java deleted file mode 100644 index dd65ddb382dc..000000000000 --- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2014 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 androidx.test.filters; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Indicates that a specific test or class requires a minimum or maximum API Level to execute. - * - * <p>Test(s) will be skipped when executed on android platforms less/more than specified level - * (inclusive). - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE, ElementType.METHOD}) -public @interface SdkSuppress { - /** The minimum API level to execute (inclusive) */ - int minSdkVersion() default 1; - /** The maximum API level to execute (inclusive) */ - int maxSdkVersion() default Integer.MAX_VALUE; - /** - * The {@link android.os.Build.VERSION.CODENAME} to execute on. This is intended to be used to run - * on a pre-release SDK, where the {@link android.os.Build.VERSION.SDK_INT} has not yet been - * finalized. This is treated as an OR operation with respect to the minSdkVersion and - * maxSdkVersion attributes. - * - * <p>For example, to filter a test so it runs on only the prerelease R SDK: <code> - * {@literal @}SdkSuppress(minSdkVersion = Build.VERSION_CODES.R, codeName = "R") - * </code> - */ - String codeName() default "unset"; -} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java deleted file mode 100644 index dd32df44effe..000000000000 --- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2016 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 androidx.test.filters; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to assign a small test size qualifier to a test. This annotation can be used at a - * method or class level. - * - * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a - * test suite of similar run time. - * - * <p>Execution time: <200ms - * - * <p>Small tests should be run very frequently. Focused on units of code to verify specific logical - * conditions. These tests should runs in an isolated environment and use mock objects for external - * dependencies. Resource access (such as file system, network, or databases) are not permitted. - * Tests that interact with hardware, make binder calls, or that facilitate android instrumentation - * should not use this annotation. - * - * <p>Note: This class replaces the deprecated Android platform size qualifier <a - * href="http://developer.android.com/reference/android/test/suitebuilder/annotation/SmallTest.html"> - * android.test.suitebuilder.annotation.SmallTest</a> and is the recommended way to annotate tests - * written with the AndroidX Test Library. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.TYPE}) -public @interface SmallTest {} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java deleted file mode 100644 index 88e636c2dd77..000000000000 --- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2016 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 androidx.test.filters; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Use this annotation on test classes or test methods that should not be included in a test suite. - * If the annotation appears on the class then no tests in that class will be included. If the - * annotation appears only on a test method then only that method will be excluded. - * - * <p>Note: This class replaces the deprecated Android platform annotation <a - * href="http://developer.android.com/reference/android/test/suitebuilder/annotation/Suppress.html"> - * android.test.suitebuilder.annotation.Suppress</a> and is the recommended way to suppress tests - * written with the AndroidX Test Library. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.TYPE}) -public @interface Suppress {} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java deleted file mode 100644 index e1379390f98b..000000000000 --- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java +++ /dev/null @@ -1,24 +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 androidx.test.runner; - -import org.junit.runners.model.InitializationError; - -public class AndroidJUnit4 extends androidx.test.ext.junit.runners.AndroidJUnit4 { - public AndroidJUnit4(Class<?> testClass) throws InitializationError { - super(testClass); - } -} diff --git a/tools/hoststubgen/hoststubgen/test-framework/Android.bp b/tools/hoststubgen/hoststubgen/test-framework/Android.bp deleted file mode 100644 index 2b91cc161b7f..000000000000 --- a/tools/hoststubgen/hoststubgen/test-framework/Android.bp +++ /dev/null @@ -1,19 +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 { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -build = ["AndroidHostTest.bp"] diff --git a/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp b/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp deleted file mode 100644 index 1f8382ad468d..000000000000 --- a/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp +++ /dev/null @@ -1,57 +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. - -// Add `build = ["AndroidHostTest.bp"]` to Android.bp to include this file. - -// Compile the test jar, using 2 rules. -// 1. Build the test against the stub. -java_library_host { - name: "HostStubGenTest-framework-test-host-test-lib", - defaults: ["hosttest-with-framework-all-hidden-api-test-lib-defaults"], - srcs: [ - "src/**/*.java", - ], - static_libs: [ - "junit", - "truth", - "mockito", - - // http://cs/h/googleplex-android/platform/superproject/main/+/main:platform_testing/libraries/annotations/src/android/platform/test/annotations/ - "platform-test-annotations", - "hoststubgen-annotations", - ], -} - -// 2. Link the above module with necessary runtime dependencies, so it can be executed stand-alone. -java_test_host { - name: "HostStubGenTest-framework-all-test-host-test", - defaults: ["hosttest-with-framework-all-hidden-api-test-defaults"], - static_libs: [ - "HostStubGenTest-framework-test-host-test-lib", - ], - test_suites: ["general-tests"], -} - -// "Productionized" build rule. -android_ravenwood_test { - name: "HostStubGenTest-framework-test", - srcs: [ - "src/**/*.java", - ], - static_libs: [ - "junit", - "truth", - "mockito", - ], -} diff --git a/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml b/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml deleted file mode 100644 index f35dcf69ecc9..000000000000 --- a/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?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. ---> - -<!-- [Ravenwood] Copied from $ANDROID_BUILD_TOP/cts/hostsidetests/devicepolicy/AndroidTest.xml --> -<configuration description="CtsContentTestCases host-side test"> - <option name="test-suite-tag" value="ravenwood" /> - <option name="config-descriptor:metadata" key="component" value="framework" /> - <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> - <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> - <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" /> - <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" /> - - <test class="com.android.tradefed.testtype.IsolatedHostTest" > - <option name="jar" value="HostStubGenTest-framework-all-test-host-test.jar" /> - </test> -</configuration> diff --git a/tools/hoststubgen/hoststubgen/test-framework/README.md b/tools/hoststubgen/hoststubgen/test-framework/README.md deleted file mode 100644 index 26a9ad13f746..000000000000 --- a/tools/hoststubgen/hoststubgen/test-framework/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# HostStubGen: (obsolete) real framework test - -This directory contains tests against the actual framework.jar code. The tests were -copied from somewhere else in the android tree. We use this directory to quickly run existing -tests. - -This directory was used during the prototype phase, but now that we have real ravenwood tests, -this directory is obsolete and should be deleted. - -## How to run - -- With `atest`. This is the proper way to run it, but it may fail due to atest's known problems. - -``` -$ atest HostStubGenTest-framework-all-test-host-test -``` - -- Advanced option: `run-test-without-atest.sh` runs the test without using `atest` - -``` -$ ./run-test-without-atest.sh -```
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh b/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh deleted file mode 100755 index cfc06a12e881..000000000000 --- a/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/bash -# 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. - -# Run HostStubGenTest-framework-test-host-test directly with JUnit. -# (without using atest.) - -source "${0%/*}"/../../common.sh - - -# Options: -# -v enable verbose log -# -d enable debugger - -verbose=0 -debug=0 -while getopts "vd" opt; do - case "$opt" in - v) verbose=1 ;; - d) debug=1 ;; - esac -done -shift $(($OPTIND - 1)) - - -if (( $verbose )) ; then - JAVA_OPTS="$JAVA_OPTS -verbose:class" -fi - -if (( $debug )) ; then - JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8700" -fi - -#======================================= -module=HostStubGenTest-framework-all-test-host-test -module_jar=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/$module/$module.jar -run m $module - -out=out - -rm -fr $out -mkdir -p $out - - -# Copy and extract the relevant jar files so we can look into them. -run cp \ - $module_jar \ - $SOONG_INT/frameworks/base/tools/hoststubgen/hoststubgen/framework-all-hidden-api-host/linux_glibc_common/gen/*.jar \ - $out - -run extract $out/*.jar - -# Result is the number of failed tests. -result=0 - - -# This suite runs all tests in the JAR. -tests=(com.android.hoststubgen.hosthelper.HostTestSuite) - -# Uncomment this to run a specific test. -# tests=(com.android.hoststubgen.frameworktest.LogTest) - - -for class in ${tests[@]} ; do - echo "Running $class ..." - - run cd "${module_jar%/*}" - run $JAVA $JAVA_OPTS \ - -cp $module_jar \ - org.junit.runner.JUnitCore \ - $class || result=$(( $result + 1 )) -done - -exit $result diff --git a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java b/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java deleted file mode 100644 index 2c5949c1c630..000000000000 --- a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java +++ /dev/null @@ -1,472 +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.hoststubgen.frameworktest; - -// [ravewnwood] Copied from cts/, and commented out unsupported stuff. - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import android.util.ArrayMap; -import android.util.Log; - -import org.junit.Test; - -import java.util.AbstractMap; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.function.BiFunction; - -/** - * Some basic tests for {@link android.util.ArrayMap}. - */ -public class ArrayMapTest { - static final boolean DEBUG = false; - - private static boolean compare(Object v1, Object v2) { - if (v1 == null) { - return v2 == null; - } - if (v2 == null) { - return false; - } - return v1.equals(v2); - } - - private static void compareMaps(HashMap map, ArrayMap array) { - if (map.size() != array.size()) { - fail("Bad size: expected " + map.size() + ", got " + array.size()); - } - - Set<Entry> mapSet = map.entrySet(); - for (Map.Entry entry : mapSet) { - Object expValue = entry.getValue(); - Object gotValue = array.get(entry.getKey()); - if (!compare(expValue, gotValue)) { - fail("Bad value: expected " + expValue + ", got " + gotValue - + " at key " + entry.getKey()); - } - } - - for (int i = 0; i < array.size(); i++) { - Object gotValue = array.valueAt(i); - Object key = array.keyAt(i); - Object expValue = map.get(key); - if (!compare(expValue, gotValue)) { - fail("Bad value: expected " + expValue + ", got " + gotValue - + " at key " + key); - } - } - - if (map.entrySet().hashCode() != array.entrySet().hashCode()) { - fail("Entry set hash codes differ: map=0x" - + Integer.toHexString(map.entrySet().hashCode()) + " array=0x" - + Integer.toHexString(array.entrySet().hashCode())); - } - - if (!map.entrySet().equals(array.entrySet())) { - fail("Failed calling equals on map entry set against array set"); - } - - if (!array.entrySet().equals(map.entrySet())) { - fail("Failed calling equals on array entry set against map set"); - } - - if (map.keySet().hashCode() != array.keySet().hashCode()) { - fail("Key set hash codes differ: map=0x" - + Integer.toHexString(map.keySet().hashCode()) + " array=0x" - + Integer.toHexString(array.keySet().hashCode())); - } - - if (!map.keySet().equals(array.keySet())) { - fail("Failed calling equals on map key set against array set"); - } - - if (!array.keySet().equals(map.keySet())) { - fail("Failed calling equals on array key set against map set"); - } - - if (!map.keySet().containsAll(array.keySet())) { - fail("Failed map key set contains all of array key set"); - } - - if (!array.keySet().containsAll(map.keySet())) { - fail("Failed array key set contains all of map key set"); - } - - if (!array.containsAll(map.keySet())) { - fail("Failed array contains all of map key set"); - } - - if (!map.entrySet().containsAll(array.entrySet())) { - fail("Failed map entry set contains all of array entry set"); - } - - if (!array.entrySet().containsAll(map.entrySet())) { - fail("Failed array entry set contains all of map entry set"); - } - } - - private static void validateArrayMap(ArrayMap array) { - Set<Map.Entry> entrySet = array.entrySet(); - int index = 0; - Iterator<Entry> entryIt = entrySet.iterator(); - while (entryIt.hasNext()) { - Map.Entry entry = entryIt.next(); - Object value = entry.getKey(); - Object realValue = array.keyAt(index); - if (!compare(realValue, value)) { - fail("Bad array map entry set: expected key " + realValue - + ", got " + value + " at index " + index); - } - value = entry.getValue(); - realValue = array.valueAt(index); - if (!compare(realValue, value)) { - fail("Bad array map entry set: expected value " + realValue - + ", got " + value + " at index " + index); - } - index++; - } - - index = 0; - Set keySet = array.keySet(); - Iterator keyIt = keySet.iterator(); - while (keyIt.hasNext()) { - Object value = keyIt.next(); - Object realValue = array.keyAt(index); - if (!compare(realValue, value)) { - fail("Bad array map key set: expected key " + realValue - + ", got " + value + " at index " + index); - } - index++; - } - - index = 0; - Collection valueCol = array.values(); - Iterator valueIt = valueCol.iterator(); - while (valueIt.hasNext()) { - Object value = valueIt.next(); - Object realValue = array.valueAt(index); - if (!compare(realValue, value)) { - fail("Bad array map value col: expected value " + realValue - + ", got " + value + " at index " + index); - } - index++; - } - } - - private static void dump(Map map, ArrayMap array) { - Log.e("test", "HashMap of " + map.size() + " entries:"); - Set<Map.Entry> mapSet = map.entrySet(); - for (Map.Entry entry : mapSet) { - Log.e("test", " " + entry.getKey() + " -> " + entry.getValue()); - } - Log.e("test", "ArrayMap of " + array.size() + " entries:"); - for (int i = 0; i < array.size(); i++) { - Log.e("test", " " + array.keyAt(i) + " -> " + array.valueAt(i)); - } - } - - private static void dump(ArrayMap map1, ArrayMap map2) { - Log.e("test", "ArrayMap of " + map1.size() + " entries:"); - for (int i = 0; i < map1.size(); i++) { - Log.e("test", " " + map1.keyAt(i) + " -> " + map1.valueAt(i)); - } - Log.e("test", "ArrayMap of " + map2.size() + " entries:"); - for (int i = 0; i < map2.size(); i++) { - Log.e("test", " " + map2.keyAt(i) + " -> " + map2.valueAt(i)); - } - } - - @Test - public void testCopyArrayMap() { - // map copy constructor test - ArrayMap newMap = new ArrayMap<Integer, String>(); - for (int i = 0; i < 10; ++i) { - newMap.put(i, String.valueOf(i)); - } - ArrayMap mapCopy = new ArrayMap(newMap); - if (!compare(mapCopy, newMap)) { - String msg = "ArrayMap copy constructor failure: expected " + - newMap + ", got " + mapCopy; - Log.e("test", msg); - dump(newMap, mapCopy); - fail(msg); - return; - } - } - - @Test - public void testEqualsArrayMap() { - ArrayMap<Integer, String> map1 = new ArrayMap<>(); - ArrayMap<Integer, String> map2 = new ArrayMap<>(); - HashMap<Integer, String> map3 = new HashMap<>(); - if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) { - fail("ArrayMap equals failure for empty maps " + map1 + ", " + - map2 + ", " + map3); - } - - for (int i = 0; i < 10; ++i) { - String value = String.valueOf(i); - map1.put(i, value); - map2.put(i, value); - map3.put(i, value); - } - if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) { - fail("ArrayMap equals failure for populated maps " + map1 + ", " + - map2 + ", " + map3); - } - - map1.remove(0); - if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) { - fail("ArrayMap equals failure for map size " + map1 + ", " + - map2 + ", " + map3); - } - - map1.put(0, "-1"); - if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) { - fail("ArrayMap equals failure for map contents " + map1 + ", " + - map2 + ", " + map3); - } - } - - private static void checkEntrySetToArray(ArrayMap<?, ?> testMap) { - try { - testMap.entrySet().toArray(); - fail(); - } catch (UnsupportedOperationException expected) { - } - - try { - Map.Entry<?, ?>[] entries = new Map.Entry[20]; - testMap.entrySet().toArray(entries); - fail(); - } catch (UnsupportedOperationException expected) { - } - } - - // http://b/32294038, Test ArrayMap.entrySet().toArray() - @Test - public void testEntrySetArray() { - // Create - ArrayMap<Integer, String> testMap = new ArrayMap<>(); - - // Test empty - checkEntrySetToArray(testMap); - - // Test non-empty - for (int i = 0; i < 10; ++i) { - testMap.put(i, String.valueOf(i)); - } - checkEntrySetToArray(testMap); - } - - @Test - public void testCanNotIteratePastEnd_entrySetIterator() { - Map<String, String> map = new ArrayMap<>(); - map.put("key 1", "value 1"); - map.put("key 2", "value 2"); - Set<Map.Entry<String, String>> expectedEntriesToIterate = new HashSet<>(Arrays.asList( - entryOf("key 1", "value 1"), - entryOf("key 2", "value 2") - )); - Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator(); - - // Assert iteration over the expected two entries in any order - assertTrue(iterator.hasNext()); - Map.Entry<String, String> firstEntry = copyOf(iterator.next()); - assertTrue(expectedEntriesToIterate.remove(firstEntry)); - - assertTrue(iterator.hasNext()); - Map.Entry<String, String> secondEntry = copyOf(iterator.next()); - assertTrue(expectedEntriesToIterate.remove(secondEntry)); - - assertFalse(iterator.hasNext()); - - try { - iterator.next(); - fail(); - } catch (NoSuchElementException expected) { - } - } - - private static <K, V> Map.Entry<K, V> entryOf(K key, V value) { - return new AbstractMap.SimpleEntry<>(key, value); - } - - private static <K, V> Map.Entry<K, V> copyOf(Map.Entry<K, V> entry) { - return entryOf(entry.getKey(), entry.getValue()); - } - - @Test - public void testCanNotIteratePastEnd_keySetIterator() { - Map<String, String> map = new ArrayMap<>(); - map.put("key 1", "value 1"); - map.put("key 2", "value 2"); - Set<String> expectedKeysToIterate = new HashSet<>(Arrays.asList("key 1", "key 2")); - Iterator<String> iterator = map.keySet().iterator(); - - // Assert iteration over the expected two keys in any order - assertTrue(iterator.hasNext()); - String firstKey = iterator.next(); - assertTrue(expectedKeysToIterate.remove(firstKey)); - - assertTrue(iterator.hasNext()); - String secondKey = iterator.next(); - assertTrue(expectedKeysToIterate.remove(secondKey)); - - assertFalse(iterator.hasNext()); - - try { - iterator.next(); - fail(); - } catch (NoSuchElementException expected) { - } - } - - @Test - public void testCanNotIteratePastEnd_valuesIterator() { - Map<String, String> map = new ArrayMap<>(); - map.put("key 1", "value 1"); - map.put("key 2", "value 2"); - Set<String> expectedValuesToIterate = new HashSet<>(Arrays.asList("value 1", "value 2")); - Iterator<String> iterator = map.values().iterator(); - - // Assert iteration over the expected two values in any order - assertTrue(iterator.hasNext()); - String firstValue = iterator.next(); - assertTrue(expectedValuesToIterate.remove(firstValue)); - - assertTrue(iterator.hasNext()); - String secondValue = iterator.next(); - assertTrue(expectedValuesToIterate.remove(secondValue)); - - assertFalse(iterator.hasNext()); - - try { - iterator.next(); - fail(); - } catch (NoSuchElementException expected) { - } - } - - @Test - public void testForEach() { - ArrayMap<String, Integer> map = new ArrayMap<>(); - - for (int i = 0; i < 50; ++i) { - map.put(Integer.toString(i), i * 10); - } - - // Make sure forEach goes through all of the elements. - HashMap<String, Integer> seen = new HashMap<>(); - map.forEach(seen::put); - compareMaps(seen, map); - } - - /** - * The entrySet Iterator returns itself from each call to {@code next()}. This is unusual - * behavior for {@link Iterator#next()}; this test ensures that any future change to this - * behavior is deliberate. - */ - @Test - public void testUnusualBehavior_eachEntryIsSameAsIterator_entrySetIterator() { - Map<String, String> map = new ArrayMap<>(); - map.put("key 1", "value 1"); - map.put("key 2", "value 2"); - Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator(); - - assertSame(iterator, iterator.next()); - assertSame(iterator, iterator.next()); - } - - @SuppressWarnings("SelfEquals") - @Test - public void testUnusualBehavior_equalsThrowsAfterRemove_entrySetIterator() { - Map<String, String> map = new ArrayMap<>(); - map.put("key 1", "value 1"); - map.put("key 2", "value 2"); - Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator(); - iterator.next(); - iterator.remove(); - try { - iterator.equals(iterator); - fail(); - } catch (IllegalStateException expected) { - } - } - - private static <T> void assertEqualsBothWays(T a, T b) { - assertEquals(a, b); - assertEquals(b, a); - assertEquals(a.hashCode(), b.hashCode()); - } - - @Test - public void testRemoveAll() { - final ArrayMap<Integer, String> map = new ArrayMap<>(); - for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) { - map.put(i, i.toString()); - } - - final ArrayMap<Integer, String> expectedMap = new ArrayMap<>(); - for (Integer i : Arrays.asList(2, 4)) { - expectedMap.put(i, String.valueOf(i)); - } - map.removeAll(Arrays.asList(0, 1, 3, 5, 6)); - if (!compare(map, expectedMap)) { - fail("ArrayMap removeAll failure, expect " + expectedMap + ", but " + map); - } - - map.removeAll(Collections.emptyList()); - if (!compare(map, expectedMap)) { - fail("ArrayMap removeAll failure for empty maps, expect " + expectedMap + ", but " + - map); - } - - map.removeAll(Arrays.asList(2, 4)); - if (!map.isEmpty()) { - fail("ArrayMap removeAll failure, expect empty, but " + map); - } - } - - @Test - public void testReplaceAll() { - final ArrayMap<Integer, Integer> map = new ArrayMap<>(); - final ArrayMap<Integer, Integer> expectedMap = new ArrayMap<>(); - final BiFunction<Integer, Integer, Integer> function = (k, v) -> 2 * v; - for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) { - map.put(i, i); - expectedMap.put(i, 2 * i); - } - - map.replaceAll(function); - if (!compare(map, expectedMap)) { - fail("ArrayMap replaceAll failure, expect " + expectedMap + ", but " + map); - } - } -} diff --git a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java b/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java deleted file mode 100644 index 3e33b54bb9f9..000000000000 --- a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java +++ /dev/null @@ -1,41 +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.hoststubgen.frameworktest; - -import static com.google.common.truth.Truth.assertThat; - -import android.util.Log; - -import org.junit.Test; - -/** - * Some basic tests for {@link android.util.Log}. - */ -public class LogTest { - @Test - public void testBasicLogging() { - Log.v("TAG", "Test v log"); - Log.d("TAG", "Test d log"); - Log.i("TAG", "Test i log"); - Log.w("TAG", "Test w log"); - Log.e("TAG", "Test e log"); - } - - @Test - public void testNativeMethods() { - assertThat(Log.isLoggable("mytag", Log.INFO)).isTrue(); - } -} diff --git a/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh b/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh deleted file mode 100755 index 72681234dad8..000000000000 --- a/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -# 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. - - -# Script to build `framework-host-stub` and `framework-host-impl`, and copy the -# generated jars to $out, and unzip them. Useful for looking into the generated files. - -source "${0%/*}"/../common.sh - -out=framework-all-stub-out - -rm -fr $out -mkdir -p $out - -# Build the jars with `m`. -run m framework-all-hidden-api-host - -# Copy the jar to out/ and extract them. -run cp \ - $SOONG_INT/frameworks/base/tools/hoststubgen/hoststubgen/framework-all-hidden-api-host/linux_glibc_common/gen/* \ - $out - -extract $out/*.jar diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh index 222c874ac34b..a6847ae97bae 100755 --- a/tools/hoststubgen/scripts/run-all-tests.sh +++ b/tools/hoststubgen/scripts/run-all-tests.sh @@ -22,7 +22,6 @@ ATEST_ARGS="--host" # These tests are known to pass. READY_TEST_MODULES=( - HostStubGenTest-framework-all-test-host-test hoststubgen-test-tiny-test CtsUtilTestCasesRavenwood CtsOsTestCasesRavenwood # This one uses native sustitution, so let's run it too. @@ -30,7 +29,6 @@ READY_TEST_MODULES=( MUST_BUILD_MODULES=( "${NOT_READY_TEST_MODULES[*]}" - HostStubGenTest-framework-test ) # First, build all the test / etc modules. This shouldn't fail. @@ -44,11 +42,8 @@ run atest $ATEST_ARGS hoststubgentest hoststubgen-invoke-test # files, and they may fail when something changes in the build system. run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh -run ./hoststubgen/test-framework/run-test-without-atest.sh - run ./hoststubgen/test-tiny-framework/run-test-manually.sh run atest $ATEST_ARGS tiny-framework-dump-test -run ./scripts/build-framework-hostside-jars-and-extract.sh # This script is already broken on goog/master # run ./scripts/build-framework-hostside-jars-without-genrules.sh |