diff options
243 files changed, 5507 insertions, 2581 deletions
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/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 008521a0446a..b043503561de 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9315,10 +9315,14 @@ package android.app.usage { public final class StorageStats implements android.os.Parcelable { method public int describeContents(); method public long getAppBytes(); + method @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public long getAppBytesByDataType(int); method public long getCacheBytes(); method public long getDataBytes(); method public long getExternalCacheBytes(); method public void writeToParcel(android.os.Parcel, int); + field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0; // 0x0 + field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1; // 0x1 + field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 2; // 0x2 field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR; } @@ -23972,6 +23976,7 @@ package android.media { method @Nullable public android.net.Uri getIconUri(); method @NonNull public String getId(); method @NonNull public CharSequence getName(); + method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus(); method public int getType(); method public int getVolume(); method public int getVolumeHandling(); @@ -23989,6 +23994,9 @@ package android.media { field public static final String FEATURE_REMOTE_VIDEO_PLAYBACK = "android.media.route.feature.REMOTE_VIDEO_PLAYBACK"; field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0 field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1 + field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2; // 0x2 + field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER = 0; // 0x0 + field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER = 1; // 0x1 field public static final int TYPE_BLE_HEADSET = 26; // 0x1a field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8 field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2 @@ -24029,6 +24037,7 @@ package android.media { method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence); method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle); method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri); + method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int); method @NonNull public android.media.MediaRoute2Info.Builder setType(int); method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic(); method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>); @@ -24242,6 +24251,7 @@ package android.media { method public void release(); method public void selectRoute(@NonNull android.media.MediaRoute2Info); method public void setVolume(int); + method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferRequestedBySelf(); } public abstract static class MediaRouter2.TransferCallback { @@ -24639,12 +24649,16 @@ package android.media { method @Nullable public CharSequence getName(); method @NonNull public java.util.List<java.lang.String> getSelectableRoutes(); method @NonNull public java.util.List<java.lang.String> getSelectedRoutes(); + method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getTransferReason(); method @NonNull public java.util.List<java.lang.String> getTransferableRoutes(); method public int getVolume(); method public int getVolumeHandling(); method public int getVolumeMax(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.RoutingSessionInfo> CREATOR; + field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_APP = 2; // 0x2 + field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_FALLBACK = 0; // 0x0 + field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_SYSTEM_REQUEST = 1; // 0x1 } public static final class RoutingSessionInfo.Builder { @@ -24665,6 +24679,8 @@ package android.media { method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferableRoute(@NonNull String); method @NonNull public android.media.RoutingSessionInfo.Builder setControlHints(@Nullable android.os.Bundle); method @NonNull public android.media.RoutingSessionInfo.Builder setName(@Nullable CharSequence); + method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.RoutingSessionInfo.Builder setTransferInitiator(@Nullable android.os.UserHandle, @Nullable String); + method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.RoutingSessionInfo.Builder setTransferReason(int); method @NonNull public android.media.RoutingSessionInfo.Builder setVolume(int); method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeHandling(int); method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeMax(int); @@ -43277,6 +43293,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"; @@ -45272,6 +45289,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(); @@ -45353,6 +45371,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 @@ -45601,6 +45622,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(); @@ -45610,9 +45633,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); @@ -53525,6 +53548,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(); @@ -53602,6 +53626,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); @@ -53946,6 +53971,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(); @@ -53957,6 +53983,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/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/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java index 8d25d7bfb11b..87d97d55318a 100644 --- a/core/java/android/app/usage/StorageStats.java +++ b/core/java/android/app/usage/StorageStats.java @@ -17,11 +17,16 @@ package android.app.usage; import android.annotation.BytesLong; +import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Storage statistics for a UID, package, or {@link UserHandle} on a single * storage volume. @@ -29,10 +34,47 @@ import android.os.UserHandle; * @see StorageStatsManager */ public final class StorageStats implements Parcelable { - /** {@hide} */ public long codeBytes; - /** {@hide} */ public long dataBytes; - /** {@hide} */ public long cacheBytes; - /** {@hide} */ public long externalCacheBytes; + /** @hide */ public long codeBytes; + /** @hide */ public long dataBytes; + /** @hide */ public long cacheBytes; + /** @hide */ public long apkBytes; + /** @hide */ public long libBytes; + /** @hide */ public long dmBytes; + /** @hide */ public long externalCacheBytes; + + /** Represents all .apk files in application code path. + * Can be used as an input to {@link #getAppBytesByDataType(int)} + * to get the sum of sizes for files of this type. + */ + @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API) + public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0; + + /** Represents all .dm files in application code path. + * Can be used as an input to {@link #getAppBytesByDataType(int)} + * to get the sum of sizes for files of this type. + */ + @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API) + public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1; + + /** Represents lib/ in application code path. + * Can be used as an input to {@link #getAppBytesByDataType(int)} + * to get the size of lib/ directory. + */ + @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API) + public static final int APP_DATA_TYPE_LIB = 2; + + /** + * Keep in sync with the file types defined above. + * @hide + */ + @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API) + @IntDef(flag = false, value = { + APP_DATA_TYPE_FILE_TYPE_APK, + APP_DATA_TYPE_FILE_TYPE_DM, + APP_DATA_TYPE_LIB, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AppDataType {} /** * Return the size of app. This includes {@code APK} files, optimized @@ -48,6 +90,27 @@ public final class StorageStats implements Parcelable { } /** + * Return the size of the specified data type. This includes files stored under + * application code path. + * <p> + * If there is more than one package inside a uid, the return represents the aggregated + * stats when query StorageStat for package or uid. + * The data is not collected and the return defaults to 0 when query StorageStats for user. + * + * <p> + * Data is isolated for each user on a multiuser device. + */ + @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API) + public long getAppBytesByDataType(@AppDataType int dataType) { + switch (dataType) { + case APP_DATA_TYPE_FILE_TYPE_APK: return apkBytes; + case APP_DATA_TYPE_LIB: return libBytes; + case APP_DATA_TYPE_FILE_TYPE_DM: return dmBytes; + default: return 0; + } + } + + /** * Return the size of all data. This includes files stored under * {@link Context#getDataDir()}, {@link Context#getCacheDir()}, * {@link Context#getCodeCacheDir()}. @@ -98,6 +161,9 @@ public final class StorageStats implements Parcelable { this.codeBytes = in.readLong(); this.dataBytes = in.readLong(); this.cacheBytes = in.readLong(); + this.apkBytes = in.readLong(); + this.libBytes = in.readLong(); + this.dmBytes = in.readLong(); this.externalCacheBytes = in.readLong(); } @@ -111,6 +177,9 @@ public final class StorageStats implements Parcelable { dest.writeLong(codeBytes); dest.writeLong(dataBytes); dest.writeLong(cacheBytes); + dest.writeLong(apkBytes); + dest.writeLong(libBytes); + dest.writeLong(dmBytes); dest.writeLong(externalCacheBytes); } diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig index a611255c3817..4d9d911ed563 100644 --- a/core/java/android/app/usage/flags.aconfig +++ b/core/java/android/app/usage/flags.aconfig @@ -35,3 +35,10 @@ flag { description: " Feature flag to support filter based event query API" bug: "194321117" } + +flag { + name: "get_app_bytes_by_data_type_api" + namespace: "system_performance" + description: "Feature flag for collecting app data size by file type API" + bug: "294088945" +} 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/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java index 81cfc078c035..b919c4b065fd 100644 --- a/core/java/android/content/pm/PackageStats.java +++ b/core/java/android/content/pm/PackageStats.java @@ -55,6 +55,18 @@ public class PackageStats implements Parcelable { /** Size of cache used by the application. (e.g., /data/data/<app>/cache) */ public long cacheSize; + /** Size of .apk files of the application. */ + /** @hide */ + public long apkSize; + + /** Size of the libraries of the application. */ + /** @hide */ + public long libSize; + + /** Size of the .dm files of the application. */ + /** @hide */ + public long dmSize; + /** * Size of the secure container on external storage holding the * application's code. @@ -108,6 +120,18 @@ public class PackageStats implements Parcelable { sb.append(" cache="); sb.append(cacheSize); } + if (apkSize != 0) { + sb.append(" apk="); + sb.append(apkSize); + } + if (libSize != 0) { + sb.append(" lib="); + sb.append(libSize); + } + if (dmSize != 0) { + sb.append(" dm="); + sb.append(dmSize); + } if (externalCodeSize != 0) { sb.append(" extCode="); sb.append(externalCodeSize); @@ -149,6 +173,9 @@ public class PackageStats implements Parcelable { codeSize = source.readLong(); dataSize = source.readLong(); cacheSize = source.readLong(); + apkSize = source.readLong(); + libSize = source.readLong(); + dmSize = source.readLong(); externalCodeSize = source.readLong(); externalDataSize = source.readLong(); externalCacheSize = source.readLong(); @@ -162,6 +189,9 @@ public class PackageStats implements Parcelable { codeSize = pStats.codeSize; dataSize = pStats.dataSize; cacheSize = pStats.cacheSize; + apkSize = pStats.apkSize; + libSize = pStats.libSize; + dmSize = pStats.dmSize; externalCodeSize = pStats.externalCodeSize; externalDataSize = pStats.externalDataSize; externalCacheSize = pStats.externalCacheSize; @@ -179,6 +209,9 @@ public class PackageStats implements Parcelable { dest.writeLong(codeSize); dest.writeLong(dataSize); dest.writeLong(cacheSize); + dest.writeLong(apkSize); + dest.writeLong(libSize); + dest.writeLong(dmSize); dest.writeLong(externalCodeSize); dest.writeLong(externalDataSize); dest.writeLong(externalCacheSize); @@ -198,6 +231,9 @@ public class PackageStats implements Parcelable { && codeSize == otherStats.codeSize && dataSize == otherStats.dataSize && cacheSize == otherStats.cacheSize + && apkSize == otherStats.apkSize + && libSize == otherStats.libSize + && dmSize == otherStats.dmSize && externalCodeSize == otherStats.externalCodeSize && externalDataSize == otherStats.externalDataSize && externalCacheSize == otherStats.externalCacheSize @@ -208,7 +244,8 @@ public class PackageStats implements Parcelable { @Override public int hashCode() { return Objects.hash(packageName, userHandle, codeSize, dataSize, - cacheSize, externalCodeSize, externalDataSize, externalCacheSize, externalMediaSize, + apkSize, libSize, dmSize, cacheSize, externalCodeSize, + externalDataSize, externalCacheSize, externalMediaSize, externalObbSize); } diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index ffd7212a7525..86024ea41363 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -751,6 +751,13 @@ public abstract class DisplayManagerInternal { */ boolean blockScreenOn(Runnable unblocker); + /** Get the current brightness levels used to determine automatic brightness based on lux + * levels. */ + float[] getCurrentAutoBrightnessLevels(); + + /** Get the current lux levels used to determine automatic brightness. */ + float[] getCurrentAutoBrightnessLuxLevels(); + /** Returns whether displayoffload supports the given display state. */ static boolean isSupportedOffloadState(int displayState) { return Display.isSuspendedState(displayState); 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/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index cbbe7856178d..b957b31a5bb7 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -22,7 +22,6 @@ import static android.graphics.Matrix.MSKEW_X; import static android.graphics.Matrix.MSKEW_Y; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; -import static android.view.Display.INVALID_DISPLAY; import static android.view.SurfaceControlProto.HASH_CODE; import static android.view.SurfaceControlProto.LAYER_ID; import static android.view.SurfaceControlProto.NAME; @@ -38,7 +37,6 @@ import android.annotation.RequiresPermission; import android.annotation.Size; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; -import android.content.Context; import android.graphics.ColorSpace; import android.graphics.GraphicBuffer; import android.graphics.Matrix; @@ -53,13 +51,8 @@ import android.hardware.HardwareBuffer; import android.hardware.OverlayProperties; import android.hardware.SyncFence; import android.hardware.display.DeviceProductInfo; -import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; -import android.hardware.display.IDisplayManager; -import android.hardware.display.IVirtualDisplayCallback; -import android.hardware.display.VirtualDisplay; import android.hardware.graphics.common.DisplayDecorationSupport; import android.opengl.EGLDisplay; import android.opengl.EGLSync; @@ -68,8 +61,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; -import android.os.RemoteException; -import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; @@ -2355,92 +2346,6 @@ public final class SurfaceControl implements Parcelable { } /** - * Because this API is now going through {@link DisplayManager}, orientation and displayRect - * will automatically be computed based on configuration changes. Because of this, the params - * orientation and displayRect are ignored - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, - publicAlternatives = "Use {@code VirtualDisplay#resize(int, int, int)} instead.", - trackingBug = 247078497) - public static void setDisplayProjection(IBinder displayToken, int orientation, - Rect layerStackRect, Rect displayRect) { - DisplayManagerGlobal.getInstance().resizeVirtualDisplay( - IVirtualDisplayCallback.Stub.asInterface(displayToken), layerStackRect.width(), - layerStackRect.height(), 1); - } - - /** - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, - publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} with flag " - + " {@code VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR} for mirroring instead.", - trackingBug = 247078497) - public static void setDisplayLayerStack(IBinder displayToken, int layerStack) { - IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE); - if (b == null) { - throw new UnsupportedOperationException(); - } - - IDisplayManager dm = IDisplayManager.Stub.asInterface(b); - try { - dm.setDisplayIdToMirror(displayToken, layerStack); - } catch (RemoteException e) { - throw new UnsupportedOperationException(e); - } - } - - /** - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, - publicAlternatives = "Use {@code VirtualDisplay#setSurface(Surface)} instead.", - trackingBug = 247078497) - public static void setDisplaySurface(IBinder displayToken, Surface surface) { - IVirtualDisplayCallback virtualDisplayCallback = - IVirtualDisplayCallback.Stub.asInterface(displayToken); - DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); - dm.setVirtualDisplaySurface(virtualDisplayCallback, surface); - } - - /** - * Secure is no longer supported because this is only called from outside system which cannot - * create secure displays. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, - publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} or " - + "{@code DisplayManager#createVirtualDisplay()} instead.", - trackingBug = 247078497) - public static IBinder createDisplay(String name, boolean secure) { - if (name == null) { - throw new IllegalArgumentException("name must not be null"); - } - - // We don't have a size yet so pass in 1 for width and height since 0 is invalid - VirtualDisplay vd = DisplayManager.createVirtualDisplay(name, 1 /* width */, 1 /* height */, - INVALID_DISPLAY, null /* Surface */); - return vd == null ? null : vd.getToken().asBinder(); - } - - /** - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, - publicAlternatives = "Use {@code VirtualDisplay#release()} instead.", - trackingBug = 247078497) - public static void destroyDisplay(IBinder displayToken) { - if (displayToken == null) { - throw new IllegalArgumentException("displayToken must not be null"); - } - - DisplayManagerGlobal.getInstance().releaseVirtualDisplay( - IVirtualDisplayCallback.Stub.asInterface(displayToken)); - } - - /** * Returns whether protected content is supported in GPU composition. * @hide */ 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/res/res/values/config.xml b/core/res/res/values/config.xml index d0de5f0e4104..5400c5848f02 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6862,4 +6862,8 @@ <!-- Whether the media player is shown on the quick settings --> <bool name="config_quickSettingsShowMediaPlayer">true</bool> + + <!-- Defines suitability of the built-in speaker route. + Refer to {@link MediaRoute2Info} to see supported values. --> + <integer name="config_mediaRouter_builtInSpeakerSuitability">0</integer> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 38943306b962..eeef19291c4f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5306,4 +5306,7 @@ <java-symbol type="bool" name="config_viewBasedRotaryEncoderHapticsEnabled" /> <java-symbol type="bool" name="config_quickSettingsShowMediaPlayer" /> + + <!-- Android MediaRouter framework configs. --> + <java-symbol type="integer" name="config_mediaRouter_builtInSpeakerSuitability" /> </resources> 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/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/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/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/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/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl index 29bfd1acae17..e2dddad274e1 100644 --- a/media/java/android/media/IMediaRouter2.aidl +++ b/media/java/android/media/IMediaRouter2.aidl @@ -19,6 +19,7 @@ package android.media; import android.media.MediaRoute2Info; import android.media.RoutingSessionInfo; import android.os.Bundle; +import android.os.UserHandle; /** * @hide @@ -35,5 +36,6 @@ oneway interface IMediaRouter2 { * Call MediaRouterService#requestCreateSessionWithRouter2 to pass the result. */ void requestCreateSessionByManager(long uniqueRequestId, in RoutingSessionInfo oldSession, - in MediaRoute2Info route); + in MediaRoute2Info route, in UserHandle transferInitiatorUserHandle, + in String transferInitiatorPackageName); } diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index fa4d1a1ff935..04e99ea8a57f 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -64,7 +64,8 @@ interface IMediaRouterService { void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId, in RoutingSessionInfo oldSession, in MediaRoute2Info route, - in @nullable Bundle sessionHints); + in @nullable Bundle sessionHints, in UserHandle transferInitiatorUserHandle, + in String transferInitiatorPackageName); void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route); void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route); void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId, @@ -84,13 +85,16 @@ interface IMediaRouterService { void stopScan(IMediaRouter2Manager manager); void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, - in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route); + in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route, + in UserHandle transferInitiatorUserHandle, in String transferInitiatorPackageName); void selectRouteWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, in MediaRoute2Info route); void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, in MediaRoute2Info route); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)") void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId, - String sessionId, in MediaRoute2Info route); + String sessionId, in MediaRoute2Info route, + in UserHandle transferInitiatorUserHandle, String transferInitiatorPackageName); void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, int volume); void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String sessionId); diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 8ad35876989d..0eabe66e9a69 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -19,6 +19,7 @@ package android.media; import static android.media.MediaRouter2Utils.toUniqueId; import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER; +import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES; import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES; import android.annotation.FlaggedApi; @@ -479,6 +480,37 @@ public final class MediaRoute2Info implements Parcelable { public static final String FEATURE_REMOTE_GROUP_PLAYBACK = "android.media.route.feature.REMOTE_GROUP_PLAYBACK"; + /** Indicates the route is always suitable for media playback. */ + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public static final int SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER = 0; + + /** + * Indicates that the route is suitable for media playback only after explicit user selection. + */ + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public static final int SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER = 1; + + /** Indicates that the route is never suitable for media playback. */ + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2; + + /** + * Route suitability status. + * + * <p>Signals whether the route is suitable to play media. + * + * @hide + */ + @IntDef( + value = { + SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER, + SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER, + SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER + }) + @Retention(RetentionPolicy.SOURCE) + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public @interface SuitabilityStatus {} + private final String mId; private final CharSequence mName; private final List<String> mFeatures; @@ -500,6 +532,7 @@ public final class MediaRoute2Info implements Parcelable { private final String mProviderId; private final boolean mIsVisibilityRestricted; private final Set<String> mAllowedPackages; + @SuitabilityStatus private final int mSuitabilityStatus; MediaRoute2Info(@NonNull Builder builder) { mId = builder.mId; @@ -521,6 +554,7 @@ public final class MediaRoute2Info implements Parcelable { mProviderId = builder.mProviderId; mIsVisibilityRestricted = builder.mIsVisibilityRestricted; mAllowedPackages = builder.mAllowedPackages; + mSuitabilityStatus = builder.mSuitabilityStatus; } MediaRoute2Info(@NonNull Parcel in) { @@ -544,6 +578,7 @@ public final class MediaRoute2Info implements Parcelable { mProviderId = in.readString(); mIsVisibilityRestricted = in.readBoolean(); mAllowedPackages = Set.of(in.createString8Array()); + mSuitabilityStatus = in.readInt(); } /** @@ -778,6 +813,13 @@ public final class MediaRoute2Info implements Parcelable { || mAllowedPackages.contains(packageName); } + /** Returns the route suitability status. */ + @SuitabilityStatus + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public int getSuitabilityStatus() { + return mSuitabilityStatus; + } + /** * Dumps the current state of the object to the given {@code pw} as a human-readable string. * @@ -809,6 +851,7 @@ public final class MediaRoute2Info implements Parcelable { pw.println(indent + "mProviderId=" + mProviderId); pw.println(indent + "mIsVisibilityRestricted=" + mIsVisibilityRestricted); pw.println(indent + "mAllowedPackages=" + mAllowedPackages); + pw.println(indent + "mSuitabilityStatus=" + mSuitabilityStatus); } private void dumpVolume(@NonNull PrintWriter pw, @NonNull String prefix) { @@ -861,39 +904,74 @@ public final class MediaRoute2Info implements Parcelable { && Objects.equals(mDeduplicationIds, other.mDeduplicationIds) && Objects.equals(mProviderId, other.mProviderId) && (mIsVisibilityRestricted == other.mIsVisibilityRestricted) - && Objects.equals(mAllowedPackages, other.mAllowedPackages); + && Objects.equals(mAllowedPackages, other.mAllowedPackages) + && mSuitabilityStatus == other.mSuitabilityStatus; } @Override public int hashCode() { // Note: mExtras is not included. - return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription, - mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax, - mVolume, mAddress, mDeduplicationIds, mProviderId, mIsVisibilityRestricted, - mAllowedPackages); + return Objects.hash( + mId, + mName, + mFeatures, + mType, + mIsSystem, + mIconUri, + mDescription, + mConnectionState, + mClientPackageName, + mPackageName, + mVolumeHandling, + mVolumeMax, + mVolume, + mAddress, + mDeduplicationIds, + mProviderId, + mIsVisibilityRestricted, + mAllowedPackages, + mSuitabilityStatus); } @Override public String toString() { // Note: mExtras is not printed here. - StringBuilder result = new StringBuilder() - .append("MediaRoute2Info{ ") - .append("id=").append(getId()) - .append(", name=").append(getName()) - .append(", features=").append(getFeatures()) - .append(", iconUri=").append(getIconUri()) - .append(", description=").append(getDescription()) - .append(", connectionState=").append(getConnectionState()) - .append(", clientPackageName=").append(getClientPackageName()) - .append(", volumeHandling=").append(getVolumeHandling()) - .append(", volumeMax=").append(getVolumeMax()) - .append(", volume=").append(getVolume()) - .append(", address=").append(getAddress()) - .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds())) - .append(", providerId=").append(getProviderId()) - .append(", isVisibilityRestricted=").append(mIsVisibilityRestricted) - .append(", allowedPackages=").append(String.join(",", mAllowedPackages)) - .append(" }"); + StringBuilder result = + new StringBuilder() + .append("MediaRoute2Info{ ") + .append("id=") + .append(getId()) + .append(", name=") + .append(getName()) + .append(", features=") + .append(getFeatures()) + .append(", iconUri=") + .append(getIconUri()) + .append(", description=") + .append(getDescription()) + .append(", connectionState=") + .append(getConnectionState()) + .append(", clientPackageName=") + .append(getClientPackageName()) + .append(", volumeHandling=") + .append(getVolumeHandling()) + .append(", volumeMax=") + .append(getVolumeMax()) + .append(", volume=") + .append(getVolume()) + .append(", address=") + .append(getAddress()) + .append(", deduplicationIds=") + .append(String.join(",", getDeduplicationIds())) + .append(", providerId=") + .append(getProviderId()) + .append(", isVisibilityRestricted=") + .append(mIsVisibilityRestricted) + .append(", allowedPackages=") + .append(String.join(",", mAllowedPackages)) + .append(", suitabilityStatus=") + .append(mSuitabilityStatus) + .append(" }"); return result.toString(); } @@ -923,6 +1001,7 @@ public final class MediaRoute2Info implements Parcelable { dest.writeString(mProviderId); dest.writeBoolean(mIsVisibilityRestricted); dest.writeString8Array(mAllowedPackages.toArray(new String[0])); + dest.writeInt(mSuitabilityStatus); } private static String getDeviceTypeString(@Type int deviceType) { @@ -1005,6 +1084,7 @@ public final class MediaRoute2Info implements Parcelable { private String mProviderId; private boolean mIsVisibilityRestricted; private Set<String> mAllowedPackages; + @SuitabilityStatus private int mSuitabilityStatus; /** * Constructor for builder to create {@link MediaRoute2Info}. @@ -1028,6 +1108,7 @@ public final class MediaRoute2Info implements Parcelable { mFeatures = new ArrayList<>(); mDeduplicationIds = Set.of(); mAllowedPackages = Set.of(); + mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER; } /** @@ -1075,6 +1156,7 @@ public final class MediaRoute2Info implements Parcelable { mProviderId = routeInfo.mProviderId; mIsVisibilityRestricted = routeInfo.mIsVisibilityRestricted; mAllowedPackages = routeInfo.mAllowedPackages; + mSuitabilityStatus = routeInfo.mSuitabilityStatus; } /** @@ -1318,6 +1400,23 @@ public final class MediaRoute2Info implements Parcelable { } /** + * Sets route suitability status. + * + * <p>The default value is {@link + * MediaRoute2Info#SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER}. + * + * <p> Apps are not supposed to set {@link + * MediaRoute2Info#SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER}. Publishing a non-system + * route with such status throws {@link SecurityException}. + */ + @NonNull + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public Builder setSuitabilityStatus(@SuitabilityStatus int suitabilityStatus) { + mSuitabilityStatus = suitabilityStatus; + return this; + } + + /** * Builds the {@link MediaRoute2Info media route info}. * * @throws IllegalArgumentException if no features are added. diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index ba26df922f23..5e235515c852 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -17,6 +17,7 @@ package android.media; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES; import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2; import static com.android.media.flags.Flags.FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2; @@ -699,15 +700,48 @@ public final class MediaRouter2 { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) + @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) { - mImpl.transfer(controller.getRoutingSessionInfo(), route); + mImpl.transfer( + controller.getRoutingSessionInfo(), + route, + android.os.Process.myUserHandle(), + mContext.getPackageName()); + } + + /** + * Transfers the media of a routing controller to the given route. + * + * <p>This will be no-op for non-system media routers. + * + * @param controller a routing controller controlling media routing. + * @param route the route you want to transfer the media to. + * @param transferInitiatorUserHandle the user handle of the app that initiated the transfer + * request. + * @param transferInitiatorPackageName the package name of the app that initiated the transfer. + * This value is used with the user handle to populate {@link + * RoutingController#wasTransferRequestedBySelf()}. + * @hide + */ + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public void transfer( + @NonNull RoutingController controller, + @NonNull MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { + mImpl.transfer( + controller.getRoutingSessionInfo(), + route, + transferInitiatorUserHandle, + transferInitiatorPackageName); } void requestCreateController( @NonNull RoutingController controller, @NonNull MediaRoute2Info route, - long managerRequestId) { + long managerRequestId, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { final int requestId = mNextRequestId.getAndIncrement(); @@ -736,7 +770,9 @@ public final class MediaRouter2 { managerRequestId, controller.getRoutingSessionInfo(), route, - controllerHints); + controllerHints, + transferInitiatorUserHandle, + transferInitiatorPackageName); } catch (RemoteException ex) { Log.e(TAG, "createControllerForTransfer: " + "Failed to request for creating a controller.", ex); @@ -1053,7 +1089,11 @@ public final class MediaRouter2 { } void onRequestCreateControllerByManagerOnHandler( - RoutingSessionInfo oldSession, MediaRoute2Info route, long managerRequestId) { + RoutingSessionInfo oldSession, + MediaRoute2Info route, + long managerRequestId, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { Log.i( TAG, TextUtils.formatSimple( @@ -1070,7 +1110,8 @@ public final class MediaRouter2 { if (controller == null) { return; } - requestCreateController(controller, route, managerRequestId); + requestCreateController(controller, route, managerRequestId, transferInitiatorUserHandle, + transferInitiatorPackageName); } private List<MediaRoute2Info> getSortedRoutes( @@ -1469,6 +1510,21 @@ public final class MediaRouter2 { } /** + * Returns whether the transfer was requested by the calling app (as determined by comparing + * {@link UserHandle} and package name). + */ + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public boolean wasTransferRequestedBySelf() { + RoutingSessionInfo sessionInfo = getRoutingSessionInfo(); + + UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle(); + String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName(); + + return Objects.equals(android.os.Process.myUserHandle(), transferInitiatorUserHandle) + && Objects.equals(mContext.getPackageName(), transferInitiatorPackageName); + } + + /** * Returns the current {@link RoutingSessionInfo} associated to this controller. */ @NonNull @@ -1980,14 +2036,20 @@ public final class MediaRouter2 { @Override public void requestCreateSessionByManager( - long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route) { + long managerRequestId, + RoutingSessionInfo oldSession, + MediaRoute2Info route, + UserHandle transferInitiatorUserHandle, + String transferInitiatorPackageName) { mHandler.sendMessage( obtainMessage( MediaRouter2::onRequestCreateControllerByManagerOnHandler, MediaRouter2.this, oldSession, route, - managerRequestId)); + managerRequestId, + transferInitiatorUserHandle, + transferInitiatorPackageName)); } } @@ -2027,7 +2089,11 @@ public final class MediaRouter2 { void stop(); - void transfer(RoutingSessionInfo sessionInfo, MediaRoute2Info route); + void transfer( + @NonNull RoutingSessionInfo sessionInfo, + @NonNull MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName); List<RoutingController> getControllers(); @@ -2220,7 +2286,11 @@ public final class MediaRouter2 { List<RoutingSessionInfo> sessionInfos = getRoutingSessions(); RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1); - transfer(targetSession, route); + transfer( + targetSession, + route, + android.os.Process.myUserHandle(), + mContext.getPackageName()); } @Override @@ -2243,14 +2313,24 @@ public final class MediaRouter2 { * * @param sessionInfo The {@link RoutingSessionInfo routing session} to transfer. * @param route The {@link MediaRoute2Info route} to transfer to. - * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info) + * @param transferInitiatorUserHandle The user handle of the app that initiated the + * transfer. + * @param transferInitiatorPackageName The package name if of the app that initiated the + * transfer. + * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info, UserHandle, String) * @see #requestCreateSession(RoutingSessionInfo, MediaRoute2Info) */ @Override + @SuppressWarnings("AndroidFrameworkRequiresPermission") public void transfer( - @NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route) { + @NonNull RoutingSessionInfo sessionInfo, + @NonNull MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); Objects.requireNonNull(route, "route must not be null"); + Objects.requireNonNull(transferInitiatorUserHandle); + Objects.requireNonNull(transferInitiatorPackageName); Log.v( TAG, @@ -2268,9 +2348,14 @@ public final class MediaRouter2 { } if (sessionInfo.getTransferableRoutes().contains(route.getId())) { - transferToRoute(sessionInfo, route); + transferToRoute( + sessionInfo, + route, + transferInitiatorUserHandle, + transferInitiatorPackageName); } else { - requestCreateSession(sessionInfo, route); + requestCreateSession(sessionInfo, route, transferInitiatorUserHandle, + transferInitiatorPackageName); } } @@ -2282,21 +2367,30 @@ public final class MediaRouter2 { * RoutingSessionInfo routing session's} {@link RoutingSessionInfo#getTransferableRoutes() * transferable routes list}. Otherwise, the request will fail. * - * <p>Use {@link #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)} to request - * an out-of-session transfer. + * <p>Use {@link #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)} to request an + * out-of-session transfer. * * @param session The {@link RoutingSessionInfo routing session} to transfer. * @param route The {@link MediaRoute2Info route} to transfer to. Must be one of the {@link * RoutingSessionInfo routing session's} {@link * RoutingSessionInfo#getTransferableRoutes() transferable routes}. */ + @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) private void transferToRoute( - @NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) { + @NonNull RoutingSessionInfo session, + @NonNull MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { int requestId = createTransferRequest(session, route); try { mMediaRouterService.transferToRouteWithManager( - mClient, requestId, session.getId(), route); + mClient, + requestId, + session.getId(), + route, + transferInitiatorUserHandle, + transferInitiatorPackageName); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -2317,7 +2411,10 @@ public final class MediaRouter2 { * @param route The {@link MediaRoute2Info route} to transfer to. */ private void requestCreateSession( - @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) { + @NonNull RoutingSessionInfo oldSession, + @NonNull MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { if (TextUtils.isEmpty(oldSession.getClientPackageName())) { Log.w(TAG, "requestCreateSession: Can't create a session without package name."); this.onTransferFailed(oldSession, route); @@ -2328,7 +2425,12 @@ public final class MediaRouter2 { try { mMediaRouterService.requestCreateSessionWithManager( - mClient, requestId, oldSession, route); + mClient, + requestId, + oldSession, + route, + transferInitiatorUserHandle, + transferInitiatorPackageName); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -3055,7 +3157,8 @@ public final class MediaRouter2 { return; } - requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE); + requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE, + android.os.Process.myUserHandle(), mContext.getPackageName()); } @Override @@ -3071,7 +3174,11 @@ public final class MediaRouter2 { * #transferTo(MediaRoute2Info)}. */ @Override - public void transfer(RoutingSessionInfo sessionInfo, MediaRoute2Info route) { + public void transfer( + @NonNull RoutingSessionInfo sessionInfo, + @NonNull MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { // Do nothing. } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 830708cb38b2..06c0996785c2 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -18,9 +18,11 @@ package android.media; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.content.Context; import android.media.session.MediaController; import android.media.session.MediaSessionManager; @@ -28,6 +30,7 @@ import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -467,30 +470,42 @@ public final class MediaRouter2Manager { * <p>Same as {@link #transfer(RoutingSessionInfo, MediaRoute2Info)}, but resolves the routing * session based on the provided package name. */ - public void transfer(@NonNull String packageName, @NonNull MediaRoute2Info route) { + @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) + public void transfer( + @NonNull String packageName, + @NonNull MediaRoute2Info route, + @NonNull UserHandle userHandle) { Objects.requireNonNull(packageName, "packageName must not be null"); Objects.requireNonNull(route, "route must not be null"); List<RoutingSessionInfo> sessionInfos = getRoutingSessions(packageName); RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1); - transfer(targetSession, route); + transfer(targetSession, route, userHandle, packageName); } /** * Transfers a routing session to a media route. + * * <p>{@link Callback#onTransferred} or {@link Callback#onTransferFailed} will be called * depending on the result. * * @param sessionInfo the routing session info to transfer * @param route the route transfer to - * + * @param transferInitiatorUserHandle the user handle of an app initiated the transfer + * @param transferInitiatorPackageName the package name of an app initiated the transfer * @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo) * @see Callback#onTransferFailed(RoutingSessionInfo, MediaRoute2Info) */ - public void transfer(@NonNull RoutingSessionInfo sessionInfo, - @NonNull MediaRoute2Info route) { + @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) + public void transfer( + @NonNull RoutingSessionInfo sessionInfo, + @NonNull MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); Objects.requireNonNull(route, "route must not be null"); + Objects.requireNonNull(transferInitiatorUserHandle); + Objects.requireNonNull(transferInitiatorPackageName); Log.v(TAG, "Transferring routing session. session= " + sessionInfo + ", route=" + route); @@ -503,9 +518,11 @@ public final class MediaRouter2Manager { } if (sessionInfo.getTransferableRoutes().contains(route.getId())) { - transferToRoute(sessionInfo, route); + transferToRoute( + sessionInfo, route, transferInitiatorUserHandle, transferInitiatorPackageName); } else { - requestCreateSession(sessionInfo, route); + requestCreateSession(sessionInfo, route, transferInitiatorUserHandle, + transferInitiatorPackageName); } } @@ -873,19 +890,30 @@ public final class MediaRouter2Manager { * * @hide */ - private void transferToRoute(@NonNull RoutingSessionInfo session, - @NonNull MediaRoute2Info route) { + @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) + private void transferToRoute( + @NonNull RoutingSessionInfo session, + @NonNull MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { int requestId = createTransferRequest(session, route); try { mMediaRouterService.transferToRouteWithManager( - mClient, requestId, session.getId(), route); + mClient, + requestId, + session.getId(), + route, + transferInitiatorUserHandle, + transferInitiatorPackageName); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } - private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route) { + private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiationPackageName) { if (TextUtils.isEmpty(oldSession.getClientPackageName())) { Log.w(TAG, "requestCreateSession: Can't create a session without package name."); notifyTransferFailed(oldSession, route); @@ -896,7 +924,8 @@ public final class MediaRouter2Manager { try { mMediaRouterService.requestCreateSessionWithManager( - mClient, requestId, oldSession, route); + mClient, requestId, oldSession, route, transferInitiatorUserHandle, + transferInitiationPackageName); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java index a77c9432f197..d28c26df6749 100644 --- a/media/java/android/media/RoutingSessionInfo.java +++ b/media/java/android/media/RoutingSessionInfo.java @@ -16,18 +16,25 @@ package android.media; +import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Resources; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.text.TextUtils; import com.android.internal.util.Preconditions; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -55,6 +62,33 @@ public final class RoutingSessionInfo implements Parcelable { private static final String KEY_GROUP_ROUTE = "androidx.mediarouter.media.KEY_GROUP_ROUTE"; private static final String KEY_VOLUME_HANDLING = "volumeHandling"; + /** + * Indicates that the transfer happened by the default logic without explicit system's or user's + * request. + * + * <p>For example, an automatically connected Bluetooth device will have this transfer reason. + */ + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public static final int TRANSFER_REASON_FALLBACK = 0; + + /** Indicates that the transfer happened from within a privileged application. */ + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public static final int TRANSFER_REASON_SYSTEM_REQUEST = 1; + + /** Indicates that the transfer happened from a non-privileged app. */ + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public static final int TRANSFER_REASON_APP = 2; + + /** + * Indicates the transfer reason. + * + * @hide + */ + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + @IntDef(value = {TRANSFER_REASON_FALLBACK, TRANSFER_REASON_SYSTEM_REQUEST, TRANSFER_REASON_APP}) + @Retention(RetentionPolicy.SOURCE) + public @interface TransferReason {} + @NonNull final String mId; @Nullable @@ -82,6 +116,10 @@ public final class RoutingSessionInfo implements Parcelable { final Bundle mControlHints; final boolean mIsSystemSession; + @TransferReason final int mTransferReason; + + @Nullable final UserHandle mTransferInitiatorUserHandle; + @Nullable final String mTransferInitiatorPackageName; RoutingSessionInfo(@NonNull Builder builder) { Objects.requireNonNull(builder, "builder must not be null."); @@ -116,6 +154,9 @@ public final class RoutingSessionInfo implements Parcelable { volumeAdjustmentForRemoteGroupSessions); mControlHints = updateVolumeHandlingInHints(builder.mControlHints, mVolumeHandling); + mTransferReason = builder.mTransferReason; + mTransferInitiatorUserHandle = builder.mTransferInitiatorUserHandle; + mTransferInitiatorPackageName = builder.mTransferInitiatorPackageName; } RoutingSessionInfo(@NonNull Parcel src) { @@ -140,6 +181,9 @@ public final class RoutingSessionInfo implements Parcelable { mControlHints = src.readBundle(); mIsSystemSession = src.readBoolean(); + mTransferReason = src.readInt(); + mTransferInitiatorUserHandle = src.readParcelable(null, android.os.UserHandle.class); + mTransferInitiatorPackageName = src.readString(); } @Nullable @@ -330,6 +374,27 @@ public final class RoutingSessionInfo implements Parcelable { return mIsSystemSession; } + /** Returns the transfer reason for this routing session. */ + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + @TransferReason + public int getTransferReason() { + return mTransferReason; + } + + /** @hide */ + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + @Nullable + public UserHandle getTransferInitiatorUserHandle() { + return mTransferInitiatorUserHandle; + } + + /** @hide */ + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + @Nullable + public String getTransferInitiatorPackageName() { + return mTransferInitiatorPackageName; + } + @Override public int describeContents() { return 0; @@ -351,6 +416,13 @@ public final class RoutingSessionInfo implements Parcelable { dest.writeInt(mVolume); dest.writeBundle(mControlHints); dest.writeBoolean(mIsSystemSession); + dest.writeInt(mTransferReason); + if (mTransferInitiatorUserHandle != null) { + mTransferInitiatorUserHandle.writeToParcel(dest, /* flags= */ 0); + } else { + dest.writeParcelable(null, /* flags= */ 0); + } + dest.writeString(mTransferInitiatorPackageName); } /** @@ -379,6 +451,9 @@ public final class RoutingSessionInfo implements Parcelable { pw.println(indent + "mVolume=" + mVolume); pw.println(indent + "mControlHints=" + mControlHints); pw.println(indent + "mIsSystemSession=" + mIsSystemSession); + pw.println(indent + "mTransferReason=" + mTransferReason); + pw.println(indent + "mtransferInitiatorUserHandle=" + mTransferInitiatorUserHandle); + pw.println(indent + "mtransferInitiatorPackageName=" + mTransferInitiatorPackageName); } @Override @@ -406,39 +481,69 @@ public final class RoutingSessionInfo implements Parcelable { && Objects.equals(mTransferableRoutes, other.mTransferableRoutes) && (mVolumeHandling == other.mVolumeHandling) && (mVolumeMax == other.mVolumeMax) - && (mVolume == other.mVolume); + && (mVolume == other.mVolume) + && (mTransferReason == other.mTransferReason) + && Objects.equals(mTransferInitiatorUserHandle, other.mTransferInitiatorUserHandle) + && Objects.equals( + mTransferInitiatorPackageName, other.mTransferInitiatorPackageName); } @Override public int hashCode() { - return Objects.hash(mId, mName, mOwnerPackageName, mClientPackageName, mProviderId, - mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferableRoutes, - mVolumeMax, mVolumeHandling, mVolume); + return Objects.hash( + mId, + mName, + mOwnerPackageName, + mClientPackageName, + mProviderId, + mSelectedRoutes, + mSelectableRoutes, + mDeselectableRoutes, + mTransferableRoutes, + mVolumeMax, + mVolumeHandling, + mVolume, + mTransferReason, + mTransferInitiatorUserHandle, + mTransferInitiatorPackageName); } @Override public String toString() { - StringBuilder result = new StringBuilder() - .append("RoutingSessionInfo{ ") - .append("sessionId=").append(getId()) - .append(", name=").append(getName()) - .append(", clientPackageName=").append(getClientPackageName()) - .append(", selectedRoutes={") - .append(String.join(",", getSelectedRoutes())) - .append("}") - .append(", selectableRoutes={") - .append(String.join(",", getSelectableRoutes())) - .append("}") - .append(", deselectableRoutes={") - .append(String.join(",", getDeselectableRoutes())) - .append("}") - .append(", transferableRoutes={") - .append(String.join(",", getTransferableRoutes())) - .append("}") - .append(", volumeHandling=").append(getVolumeHandling()) - .append(", volumeMax=").append(getVolumeMax()) - .append(", volume=").append(getVolume()) - .append(" }"); + StringBuilder result = + new StringBuilder() + .append("RoutingSessionInfo{ ") + .append("sessionId=") + .append(getId()) + .append(", name=") + .append(getName()) + .append(", clientPackageName=") + .append(getClientPackageName()) + .append(", selectedRoutes={") + .append(String.join(",", getSelectedRoutes())) + .append("}") + .append(", selectableRoutes={") + .append(String.join(",", getSelectableRoutes())) + .append("}") + .append(", deselectableRoutes={") + .append(String.join(",", getDeselectableRoutes())) + .append("}") + .append(", transferableRoutes={") + .append(String.join(",", getTransferableRoutes())) + .append("}") + .append(", volumeHandling=") + .append(getVolumeHandling()) + .append(", volumeMax=") + .append(getVolumeMax()) + .append(", volume=") + .append(getVolume()) + .append(", transferReason=") + .append(getTransferReason()) + .append(", transferInitiatorUserHandle=") + .append(getTransferInitiatorUserHandle()) + .append(", transferInitiatorPackageName=") + .append(getTransferInitiatorPackageName()) + .append(" }"); return result.toString(); } @@ -494,6 +599,9 @@ public final class RoutingSessionInfo implements Parcelable { @Nullable private Bundle mControlHints; private boolean mIsSystemSession; + @TransferReason private int mTransferReason = TRANSFER_REASON_FALLBACK; + @Nullable private UserHandle mTransferInitiatorUserHandle; + @Nullable private String mTransferInitiatorPackageName; /** * Constructor for builder to create {@link RoutingSessionInfo}. @@ -555,6 +663,9 @@ public final class RoutingSessionInfo implements Parcelable { mControlHints = sessionInfo.mControlHints; mIsSystemSession = sessionInfo.mIsSystemSession; + mTransferReason = sessionInfo.mTransferReason; + mTransferInitiatorUserHandle = sessionInfo.mTransferInitiatorUserHandle; + mTransferInitiatorPackageName = sessionInfo.mTransferInitiatorPackageName; } /** @@ -784,6 +895,35 @@ public final class RoutingSessionInfo implements Parcelable { } /** + * Sets transfer reason for the current session. + * + * <p>By default the transfer reason is set to {@link + * RoutingSessionInfo#TRANSFER_REASON_FALLBACK}. + */ + @NonNull + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public Builder setTransferReason(@TransferReason int transferReason) { + mTransferReason = transferReason; + return this; + } + + /** + * Sets the user handle and package name of the process that initiated the transfer. + * + * <p>By default the transfer initiation user handle and package name are set to {@code + * null}. + */ + @NonNull + @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) + public Builder setTransferInitiator( + @Nullable UserHandle transferInitiatorUserHandle, + @Nullable String transferInitiatorPackageName) { + mTransferInitiatorUserHandle = transferInitiatorUserHandle; + mTransferInitiatorPackageName = transferInitiatorPackageName; + return this; + } + + /** * Builds a routing session info. * * @throws IllegalArgumentException if no selected routes are added. diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 07f63e5441af..3da52ccbafed 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -69,3 +69,11 @@ flag { description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos." bug: "314324170" } + +flag { + name: "enable_built_in_speaker_route_suitability_statuses" + namespace: "media_solutions" + description: "Make MediaRoute2Info provide information about routes suitability for transfer." + bug: "279555229" +} + diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java index 8ed4bf2b9cc3..c836df3b2c4d 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java @@ -385,7 +385,9 @@ public class MediaRouter2ManagerTest { MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1); assertThat(routeToSelect).isNotNull(); - mManager.transfer(mPackageName, routeToSelect); + mManager.transfer( + mPackageName, routeToSelect, + android.os.Process.myUserHandle()); assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); assertThat(mManager.getRemoteSessions()).hasSize(1); } @@ -411,7 +413,9 @@ public class MediaRouter2ManagerTest { assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1); - mManager.transfer(mPackageName, routeToSelect); + mManager.transfer( + mPackageName, routeToSelect, + android.os.Process.myUserHandle()); assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName); @@ -450,7 +454,11 @@ public class MediaRouter2ManagerTest { .addFeature(FEATURE_REMOTE_PLAYBACK) .build(); - mManager.transfer(mManager.getSystemRoutingSession(null), unknownRoute); + mManager.transfer( + mManager.getSystemRoutingSession(null), + unknownRoute, + android.os.Process.myUserHandle(), + mContext.getPackageName()); assertThat(onSessionCreatedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isFalse(); assertThat(onTransferFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); } @@ -484,7 +492,11 @@ public class MediaRouter2ManagerTest { assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1); assertThat(mRouter2.getControllers()).hasSize(1); - mManager.transfer(mManager.getRoutingSessions(mPackageName).get(0), routeToSelect); + mManager.transfer( + mManager.getRoutingSessions(mPackageName).get(0), + routeToSelect, + android.os.Process.myUserHandle(), + mContext.getPackageName()); assertThat(transferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(2); @@ -516,7 +528,11 @@ public class MediaRouter2ManagerTest { } }); awaitOnRouteChangedManager( - () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID1)), + () -> + mManager.transfer( + mPackageName, + routes.get(ROUTE_ID1), + android.os.Process.myUserHandle()), ROUTE_ID1, route -> TextUtils.equals(route.getClientPackageName(), mPackageName)); assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); @@ -527,7 +543,11 @@ public class MediaRouter2ManagerTest { RoutingSessionInfo sessionInfo = sessions.get(1); awaitOnRouteChangedManager( - () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)), + () -> + mManager.transfer( + mPackageName, + routes.get(ROUTE_ID5_TO_TRANSFER_TO), + android.os.Process.myUserHandle()), ROUTE_ID5_TO_TRANSFER_TO, route -> TextUtils.equals(route.getClientPackageName(), mPackageName)); @@ -585,9 +605,11 @@ public class MediaRouter2ManagerTest { assertThat(route1).isNotNull(); assertThat(route2).isNotNull(); - mManager.transfer(mPackageName, route1); + mManager.transfer( + mPackageName, route1, android.os.Process.myUserHandle()); assertThat(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); - mManager.transfer(mPackageName, route2); + mManager.transfer( + mPackageName, route2, android.os.Process.myUserHandle()); assertThat(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); // onTransferFailed/onSessionReleased should not be called. @@ -634,7 +656,11 @@ public class MediaRouter2ManagerTest { List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName); RoutingSessionInfo targetSession = sessions.get(sessions.size() - 1); - mManager.transfer(targetSession, routes.get(ROUTE_ID6_TO_BE_IGNORED)); + mManager.transfer( + targetSession, + routes.get(ROUTE_ID6_TO_BE_IGNORED), + android.os.Process.myUserHandle(), + mContext.getPackageName()); assertThat(onSessionCreatedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isFalse(); assertThat(onFailedLatch.await(MediaRouter2Manager.TRANSFER_TIMEOUT_MS, @@ -705,7 +731,10 @@ public class MediaRouter2ManagerTest { } }); - mManager.transfer(mPackageName, routes.get(ROUTE_ID1)); + mManager.transfer( + mPackageName, + routes.get(ROUTE_ID1), + android.os.Process.myUserHandle()); assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName); @@ -860,7 +889,8 @@ public class MediaRouter2ManagerTest { }); mRouter2.setOnGetControllerHintsListener(listener); - mManager.transfer(mPackageName, route); + mManager.transfer( + mPackageName, route, android.os.Process.myUserHandle()); assertThat(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); assertThat(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); @@ -905,7 +935,10 @@ public class MediaRouter2ManagerTest { } }); - mManager.transfer(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT)); + mManager.transfer( + mPackageName, + routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT), + android.os.Process.myUserHandle()); assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); } 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..97bbf12fd055 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; @@ -80,14 +81,17 @@ public class ManagerInfoMediaManager extends InfoMediaManager { @Override protected void transferToRoute(@NonNull MediaRoute2Info route) { - mRouterManager.transfer(mPackageName, route); + // TODO: b/279555229 - provide real user handle of a caller. + mRouterManager.transfer(mPackageName, route, android.os.Process.myUserHandle()); } @Override protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) { final RoutingSessionInfo info = mRouterManager.getSystemRoutingSession(null); if (info != null) { - mRouterManager.transfer(info, device.mRouteInfo); + // TODO: b/279555229 - provide real user handle and package name of a caller. + mRouterManager.transfer( + info, device.mRouteInfo, android.os.Process.myUserHandle(), mPackageName); return true; } return false; 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/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 8b27960af309..98a2d9ff2d19 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -261,3 +261,24 @@ flag { "prefer using alpha to distinguish network activity." 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/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index b9d66432b5f2..2bfa7d9bbd7b 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -33,6 +33,8 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.animation.GlyphCallback import com.android.systemui.animation.TextAnimator import com.android.systemui.customization.R +import com.android.systemui.log.core.LogcatOnlyMessageBuffer +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.core.Logger import com.android.systemui.log.core.MessageBuffer import java.io.PrintWriter @@ -51,12 +53,13 @@ class AnimatableClockView @JvmOverloads constructor( defStyleAttr: Int = 0, defStyleRes: Int = 0 ) : TextView(context, attrs, defStyleAttr, defStyleRes) { - var messageBuffer: MessageBuffer? = null - set(value) { - logger = if (value != null) Logger(value, TAG) else null - } - - private var logger: Logger? = null + // To protect us from issues from this being null while the TextView constructor is running, we + // implement the get method and ensure a value is returned before initialization is complete. + private var logger = DEFAULT_LOGGER + get() = field ?: DEFAULT_LOGGER + var messageBuffer: MessageBuffer + get() = logger.buffer + set(value) { logger = Logger(value, TAG) } private val time = Calendar.getInstance() @@ -133,8 +136,8 @@ class AnimatableClockView @JvmOverloads constructor( } override fun onAttachedToWindow() { + logger.d("onAttachedToWindow") super.onAttachedToWindow() - logger?.d("onAttachedToWindow") refreshFormat() } @@ -150,13 +153,13 @@ class AnimatableClockView @JvmOverloads constructor( time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis() contentDescription = DateFormat.format(descFormat, time) val formattedText = DateFormat.format(format, time) - logger?.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() } + logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() } // Setting text actually triggers a layout pass (because the text view is set to // wrap_content width and TextView always relayouts for this). Avoid needless // relayout if the text didn't actually change. if (!TextUtils.equals(text, formattedText)) { text = formattedText - logger?.d({ "refreshTime: done setting new time text to: $str1" }) { + logger.d({ "refreshTime: done setting new time text to: $str1" }) { str1 = formattedText?.toString() } // Because the TextLayout may mutate under the hood as a result of the new text, we @@ -165,21 +168,22 @@ class AnimatableClockView @JvmOverloads constructor( // without being notified TextInterpolator being notified. if (layout != null) { textAnimator?.updateLayout(layout) - logger?.d("refreshTime: done updating textAnimator layout") + logger.d("refreshTime: done updating textAnimator layout") } requestLayout() - logger?.d("refreshTime: after requestLayout") + logger.d("refreshTime: after requestLayout") } } fun onTimeZoneChanged(timeZone: TimeZone?) { + logger.d({ "onTimeZoneChanged($str1)" }) { str1 = timeZone?.toString() } time.timeZone = timeZone refreshFormat() - logger?.d({ "onTimeZoneChanged newTimeZone=$str1" }) { str1 = timeZone?.toString() } } @SuppressLint("DrawAllocation") override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + logger.d("onMeasure") super.onMeasure(widthMeasureSpec, heightMeasureSpec) val animator = textAnimator if (animator == null) { @@ -189,10 +193,10 @@ class AnimatableClockView @JvmOverloads constructor( } else { animator.updateLayout(layout) } - logger?.d("onMeasure") } override fun onDraw(canvas: Canvas) { + logger.d({ "onDraw($str1)"}) { str1 = text.toString() } // Use textAnimator to render text if animation is enabled. // Otherwise default to using standard draw functions. if (isAnimationEnabled) { @@ -201,22 +205,23 @@ class AnimatableClockView @JvmOverloads constructor( } else { super.onDraw(canvas) } - logger?.d("onDraw") } override fun invalidate() { + @Suppress("UNNECESSARY_SAFE_CALL") + // logger won't be initialized when called by TextView's constructor + logger.d("invalidate") super.invalidate() - logger?.d("invalidate") } override fun onTextChanged( - text: CharSequence, - start: Int, - lengthBefore: Int, - lengthAfter: Int + text: CharSequence, + start: Int, + lengthBefore: Int, + lengthAfter: Int ) { + logger.d({ "onTextChanged($str1)" }) { str1 = text.toString() } super.onTextChanged(text, start, lengthBefore, lengthAfter) - logger?.d({ "onTextChanged text=$str1" }) { str1 = text.toString() } } fun setLineSpacingScale(scale: Float) { @@ -230,7 +235,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateColorChange() { - logger?.d("animateColorChange") + logger.d("animateColorChange") setTextStyle( weight = lockScreenWeight, textSize = -1f, @@ -252,7 +257,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateAppearOnLockscreen() { - logger?.d("animateAppearOnLockscreen") + logger.d("animateAppearOnLockscreen") setTextStyle( weight = dozingWeight, textSize = -1f, @@ -278,7 +283,7 @@ class AnimatableClockView @JvmOverloads constructor( if (isAnimationEnabled && textAnimator == null) { return } - logger?.d("animateFoldAppear") + logger.d("animateFoldAppear") setTextStyle( weight = lockScreenWeightInternal, textSize = -1f, @@ -305,7 +310,7 @@ class AnimatableClockView @JvmOverloads constructor( // Skip charge animation if dozing animation is already playing. return } - logger?.d("animateCharge") + logger.d("animateCharge") val startAnimPhase2 = Runnable { setTextStyle( weight = if (isDozing()) dozingWeight else lockScreenWeight, @@ -329,7 +334,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateDoze(isDozing: Boolean, animate: Boolean) { - logger?.d("animateDoze") + logger.d("animateDoze") setTextStyle( weight = if (isDozing) dozingWeight else lockScreenWeight, textSize = -1f, @@ -448,7 +453,7 @@ class AnimatableClockView @JvmOverloads constructor( isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12 else -> DOUBLE_LINE_FORMAT_12_HOUR } - logger?.d({ "refreshFormat format=$str1" }) { str1 = format?.toString() } + logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() } descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12 refreshTime() @@ -552,6 +557,8 @@ class AnimatableClockView @JvmOverloads constructor( companion object { private val TAG = AnimatableClockView::class.simpleName!! + private val DEFAULT_LOGGER = Logger(LogcatOnlyMessageBuffer(LogLevel.WARNING), TAG) + const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600 private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm" private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm" diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index cdd074d872c0..41bde5298c66 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -21,20 +21,16 @@ import android.graphics.drawable.Drawable import android.net.Uri import android.os.UserHandle import android.provider.Settings -import android.util.Log import androidx.annotation.OpenForTesting -import com.android.systemui.log.LogMessageImpl import com.android.systemui.log.core.LogLevel -import com.android.systemui.log.core.LogMessage +import com.android.systemui.log.core.LogcatOnlyMessageBuffer import com.android.systemui.log.core.Logger -import com.android.systemui.log.core.MessageBuffer -import com.android.systemui.log.core.MessageInitializer -import com.android.systemui.log.core.MessagePrinter import com.android.systemui.plugins.PluginLifecycleManager import com.android.systemui.plugins.PluginListener import com.android.systemui.plugins.PluginManager import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockId +import com.android.systemui.plugins.clocks.ClockMessageBuffers import com.android.systemui.plugins.clocks.ClockMetadata import com.android.systemui.plugins.clocks.ClockProvider import com.android.systemui.plugins.clocks.ClockProviderPlugin @@ -77,32 +73,6 @@ private fun <TKey : Any, TVal : Any> ConcurrentHashMap<TKey, TVal>.concurrentGet return result ?: value } -private val TMP_MESSAGE: LogMessage by lazy { LogMessageImpl.Factory.create() } - -private inline fun Logger?.tryLog( - tag: String, - level: LogLevel, - messageInitializer: MessageInitializer, - noinline messagePrinter: MessagePrinter, - ex: Throwable? = null, -) { - if (this != null) { - // Wrap messagePrinter to convert it from crossinline to noinline - this.log(level, messagePrinter, ex, messageInitializer) - } else { - messageInitializer(TMP_MESSAGE) - val msg = messagePrinter(TMP_MESSAGE) - when (level) { - LogLevel.VERBOSE -> Log.v(tag, msg, ex) - LogLevel.DEBUG -> Log.d(tag, msg, ex) - LogLevel.INFO -> Log.i(tag, msg, ex) - LogLevel.WARNING -> Log.w(tag, msg, ex) - LogLevel.ERROR -> Log.e(tag, msg, ex) - LogLevel.WTF -> Log.wtf(tag, msg, ex) - } - } -} - /** ClockRegistry aggregates providers and plugins */ open class ClockRegistry( val context: Context, @@ -114,12 +84,15 @@ open class ClockRegistry( val handleAllUsers: Boolean, defaultClockProvider: ClockProvider, val fallbackClockId: ClockId = DEFAULT_CLOCK_ID, - messageBuffer: MessageBuffer? = null, + val clockBuffers: ClockMessageBuffers? = null, val keepAllLoaded: Boolean, subTag: String, var isTransitClockEnabled: Boolean = false, ) { private val TAG = "${ClockRegistry::class.simpleName} ($subTag)" + private val logger: Logger = + Logger(clockBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.DEBUG), TAG) + interface ClockChangeListener { // Called when the active clock changes fun onCurrentClockChanged() {} @@ -128,7 +101,6 @@ open class ClockRegistry( fun onAvailableClocksChanged() {} } - private val logger: Logger? = if (messageBuffer != null) Logger(messageBuffer, TAG) else null private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>() private val clockChangeListeners = mutableListOf<ClockChangeListener>() private val settingObserver = @@ -157,21 +129,15 @@ open class ClockRegistry( val knownClocks = KNOWN_PLUGINS.get(manager.getPackage()) if (knownClocks == null) { - logger.tryLog( - TAG, - LogLevel.WARNING, - { str1 = manager.getPackage() }, - { "Loading unrecognized clock package: $str1" } - ) + logger.w({ "Loading unrecognized clock package: $str1" }) { + str1 = manager.getPackage() + } return true } - logger.tryLog( - TAG, - LogLevel.INFO, - { str1 = manager.getPackage() }, - { "Skipping initial load of known clock package package: $str1" } - ) + logger.i({ "Skipping initial load of known clock package package: $str1" }) { + str1 = manager.getPackage() + } var isCurrentClock = false var isClockListChanged = false @@ -185,19 +151,14 @@ open class ClockRegistry( } if (manager != info.manager) { - logger.tryLog( - TAG, - LogLevel.ERROR, - { - str1 = id - str2 = info.manager.toString() - str3 = manager.toString() - }, - { - "Clock Id conflict on attach: " + - "$str1 is double registered by $str2 and $str3" - } - ) + logger.e({ + "Clock Id conflict on attach: " + + "$str1 is double registered by $str2 and $str3" + }) { + str1 = id + str2 = info.manager.toString() + str3 = manager.toString() + } continue } @@ -219,6 +180,8 @@ open class ClockRegistry( pluginContext: Context, manager: PluginLifecycleManager<ClockProviderPlugin> ) { + plugin.initialize(clockBuffers) + var isClockListChanged = false for (clock in plugin.getClocks()) { val id = clock.clockId @@ -233,19 +196,14 @@ open class ClockRegistry( } if (manager != info.manager) { - logger.tryLog( - TAG, - LogLevel.ERROR, - { - str1 = id - str2 = info.manager.toString() - str3 = manager.toString() - }, - { - "Clock Id conflict on load: " + - "$str1 is double registered by $str2 and $str3" - } - ) + logger.e({ + "Clock Id conflict on load: " + + "$str1 is double registered by $str2 and $str3" + }) { + str1 = id + str2 = info.manager.toString() + str3 = manager.toString() + } manager.unloadPlugin() continue } @@ -268,19 +226,14 @@ open class ClockRegistry( val id = clock.clockId val info = availableClocks[id] if (info?.manager != manager) { - logger.tryLog( - TAG, - LogLevel.ERROR, - { - str1 = id - str2 = info?.manager.toString() - str3 = manager.toString() - }, - { - "Clock Id conflict on unload: " + - "$str1 is double registered by $str2 and $str3" - } - ) + logger.e({ + "Clock Id conflict on unload: " + + "$str1 is double registered by $str2 and $str3" + }) { + str1 = id + str2 = info?.manager.toString() + str3 = manager.toString() + } continue } info.provider = null @@ -350,7 +303,7 @@ open class ClockRegistry( ClockSettings.deserialize(json) } catch (ex: Exception) { - logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex) + logger.e("Failed to parse clock settings", ex) null } settings = result @@ -379,7 +332,7 @@ open class ClockRegistry( ) } } catch (ex: Exception) { - logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex) + logger.e("Failed to set clock settings", ex) } settings = value } @@ -451,7 +404,8 @@ open class ClockRegistry( } init { - // Register default clock designs + // Initialize & register default clock designs + defaultClockProvider.initialize(clockBuffers) for (clock in defaultClockProvider.getClocks()) { availableClocks[clock.clockId] = ClockInfo(clock, defaultClockProvider, null) } @@ -514,12 +468,7 @@ open class ClockRegistry( fun verifyLoadedProviders() { val shouldSchedule = isQueued.compareAndSet(false, true) if (!shouldSchedule) { - logger.tryLog( - TAG, - LogLevel.VERBOSE, - {}, - { "verifyLoadedProviders: shouldSchedule=false" } - ) + logger.v("verifyLoadedProviders: shouldSchedule=false") return } @@ -528,12 +477,7 @@ open class ClockRegistry( synchronized(availableClocks) { isQueued.set(false) if (keepAllLoaded) { - logger.tryLog( - TAG, - LogLevel.INFO, - {}, - { "verifyLoadedProviders: keepAllLoaded=true" } - ) + logger.i("verifyLoadedProviders: keepAllLoaded=true") // Enforce that all plugins are loaded if requested for ((_, info) in availableClocks) { info.manager?.loadPlugin() @@ -543,12 +487,7 @@ open class ClockRegistry( val currentClock = availableClocks[currentClockId] if (currentClock == null) { - logger.tryLog( - TAG, - LogLevel.INFO, - {}, - { "verifyLoadedProviders: currentClock=null" } - ) + logger.i("verifyLoadedProviders: currentClock=null") // Current Clock missing, load no plugins and use default for ((_, info) in availableClocks) { info.manager?.unloadPlugin() @@ -556,12 +495,7 @@ open class ClockRegistry( return@launch } - logger.tryLog( - TAG, - LogLevel.INFO, - {}, - { "verifyLoadedProviders: load currentClock" } - ) + logger.i("verifyLoadedProviders: load currentClock") val currentManager = currentClock.manager currentManager?.loadPlugin() @@ -577,30 +511,26 @@ open class ClockRegistry( private fun onConnected(info: ClockInfo) { val isCurrent = currentClockId == info.metadata.clockId - logger.tryLog( - TAG, + logger.log( if (isCurrent) LogLevel.INFO else LogLevel.DEBUG, - { - str1 = info.metadata.clockId - str2 = info.manager.toString() - bool1 = isCurrent - }, { "Connected $str1 @$str2" + if (bool1) " (Current Clock)" else "" } - ) + ) { + str1 = info.metadata.clockId + str2 = info.manager.toString() + bool1 = isCurrent + } } private fun onLoaded(info: ClockInfo) { val isCurrent = currentClockId == info.metadata.clockId - logger.tryLog( - TAG, + logger.log( if (isCurrent) LogLevel.INFO else LogLevel.DEBUG, - { - str1 = info.metadata.clockId - str2 = info.manager.toString() - bool1 = isCurrent - }, { "Loaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" } - ) + ) { + str1 = info.metadata.clockId + str2 = info.manager.toString() + bool1 = isCurrent + } if (isCurrent) { triggerOnCurrentClockChanged() @@ -609,16 +539,14 @@ open class ClockRegistry( private fun onUnloaded(info: ClockInfo) { val isCurrent = currentClockId == info.metadata.clockId - logger.tryLog( - TAG, + logger.log( if (isCurrent) LogLevel.WARNING else LogLevel.DEBUG, - { - str1 = info.metadata.clockId - str2 = info.manager.toString() - bool1 = isCurrent - }, { "Unloaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" } - ) + ) { + str1 = info.metadata.clockId + str2 = info.manager.toString() + bool1 = isCurrent + } if (isCurrent) { triggerOnCurrentClockChanged() @@ -627,16 +555,14 @@ open class ClockRegistry( private fun onDisconnected(info: ClockInfo) { val isCurrent = currentClockId == info.metadata.clockId - logger.tryLog( - TAG, + logger.log( if (isCurrent) LogLevel.INFO else LogLevel.DEBUG, - { - str1 = info.metadata.clockId - str2 = info.manager.toString() - bool1 = isCurrent - }, { "Disconnected $str1 @$str2" + if (bool1) " (Current Clock)" else "" } - ) + ) { + str1 = info.metadata.clockId + str2 = info.manager.toString() + bool1 = isCurrent + } } fun getClocks(): List<ClockMetadata> { @@ -676,23 +602,13 @@ open class ClockRegistry( if (isEnabled && clockId.isNotEmpty()) { val clock = createClock(clockId) if (clock != null) { - logger.tryLog(TAG, LogLevel.INFO, { str1 = clockId }, { "Rendering clock $str1" }) + logger.i({ "Rendering clock $str1" }) { str1 = clockId } return clock } else if (availableClocks.containsKey(clockId)) { - logger.tryLog( - TAG, - LogLevel.WARNING, - { str1 = clockId }, - { "Clock $str1 not loaded; using default" } - ) + logger.w({ "Clock $str1 not loaded; using default" }) { str1 = clockId } verifyLoadedProviders() } else { - logger.tryLog( - TAG, - LogLevel.ERROR, - { str1 = clockId }, - { "Clock $str1 not found; using default" } - ) + logger.e({ "Clock $str1 not found; using default" }) { str1 = clockId } } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index 01c03b1f25f6..99d321695d04 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -33,6 +33,7 @@ import com.android.systemui.plugins.clocks.ClockEvents import com.android.systemui.plugins.clocks.ClockFaceConfig import com.android.systemui.plugins.clocks.ClockFaceController import com.android.systemui.plugins.clocks.ClockFaceEvents +import com.android.systemui.plugins.clocks.ClockMessageBuffers import com.android.systemui.plugins.clocks.ClockSettings import com.android.systemui.plugins.clocks.DefaultClockFaceLayout import com.android.systemui.plugins.clocks.WeatherData @@ -41,8 +42,6 @@ import java.io.PrintWriter import java.util.Locale import java.util.TimeZone -private val TAG = DefaultClockController::class.simpleName - /** * Controls the default clock visuals. * @@ -56,6 +55,7 @@ class DefaultClockController( private val settings: ClockSettings?, private val hasStepClockAnimation: Boolean = false, private val migratedClocks: Boolean = false, + messageBuffers: ClockMessageBuffers? = null, ) : ClockController { override val smallClock: DefaultClockFaceController override val largeClock: LargeClockFaceController @@ -83,13 +83,15 @@ class DefaultClockController( DefaultClockFaceController( layoutInflater.inflate(R.layout.clock_default_small, parent, false) as AnimatableClockView, - settings?.seedColor + settings?.seedColor, + messageBuffers?.smallClockMessageBuffer ) largeClock = LargeClockFaceController( layoutInflater.inflate(R.layout.clock_default_large, parent, false) as AnimatableClockView, - settings?.seedColor + settings?.seedColor, + messageBuffers?.largeClockMessageBuffer ) clocks = listOf(smallClock.view, largeClock.view) @@ -110,6 +112,7 @@ class DefaultClockController( open inner class DefaultClockFaceController( override val view: AnimatableClockView, var seedColor: Int?, + messageBuffer: MessageBuffer?, ) : ClockFaceController { // MAGENTA is a placeholder, and will be assigned correctly in initialize @@ -120,12 +123,6 @@ class DefaultClockController( override val config = ClockFaceConfig() override val layout = DefaultClockFaceLayout(view) - override var messageBuffer: MessageBuffer? - get() = view.messageBuffer - set(value) { - view.messageBuffer = value - } - override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f) internal set @@ -134,6 +131,7 @@ class DefaultClockController( currentColor = seedColor!! } view.setColors(DOZE_COLOR, currentColor) + messageBuffer?.let { view.messageBuffer = it } } override val events = @@ -188,7 +186,8 @@ class DefaultClockController( inner class LargeClockFaceController( view: AnimatableClockView, seedColor: Int?, - ) : DefaultClockFaceController(view, seedColor) { + messageBuffer: MessageBuffer?, + ) : DefaultClockFaceController(view, seedColor, messageBuffer) { override val layout = DefaultClockFaceLayout(view) override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index a219be53bd1a..20f87a059656 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -20,6 +20,7 @@ import android.view.LayoutInflater import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockId +import com.android.systemui.plugins.clocks.ClockMessageBuffers import com.android.systemui.plugins.clocks.ClockMetadata import com.android.systemui.plugins.clocks.ClockProvider import com.android.systemui.plugins.clocks.ClockSettings @@ -35,6 +36,12 @@ class DefaultClockProvider( val hasStepClockAnimation: Boolean = false, val migratedClocks: Boolean = false ) : ClockProvider { + private var messageBuffers: ClockMessageBuffers? = null + + override fun initialize(buffers: ClockMessageBuffers?) { + messageBuffers = buffers + } + override fun getClocks(): List<ClockMetadata> = listOf(ClockMetadata(DEFAULT_CLOCK_ID)) override fun createClock(settings: ClockSettings): ClockController { @@ -49,6 +56,7 @@ class DefaultClockProvider( settings, hasStepClockAnimation, migratedClocks, + messageBuffers, ) } diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt new file mode 100644 index 000000000000..006b521ad867 --- /dev/null +++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.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.log.core + +import android.util.Log +import com.android.systemui.log.LogMessageImpl + +/** + * A simple implementation of [MessageBuffer] that forwards messages to [android.util.Log] + * immediately. This defeats the intention behind [LogBuffer] and should only be used when + * [LogBuffer]s are unavailable in a certain context. + */ +class LogcatOnlyMessageBuffer( + val targetLogLevel: LogLevel, +) : MessageBuffer { + private val singleMessage = LogMessageImpl.Factory.create() + private var isObtained: Boolean = false + + @Synchronized + override fun obtain( + tag: String, + level: LogLevel, + messagePrinter: MessagePrinter, + exception: Throwable?, + ): LogMessage { + if (isObtained) { + throw UnsupportedOperationException( + "Message has already been obtained. Call order is incorrect." + ) + } + + singleMessage.reset(tag, level, System.currentTimeMillis(), messagePrinter, exception) + isObtained = true + return singleMessage + } + + @Synchronized + override fun commit(message: LogMessage) { + if (singleMessage != message) { + throw IllegalArgumentException("Message argument is not the expected message.") + } + if (!isObtained) { + throw UnsupportedOperationException( + "Message has not been obtained. Call order is incorrect." + ) + } + + if (message.level >= targetLogLevel) { + val strMessage = message.messagePrinter(message) + when (message.level) { + LogLevel.VERBOSE -> Log.v(message.tag, strMessage, message.exception) + LogLevel.DEBUG -> Log.d(message.tag, strMessage, message.exception) + LogLevel.INFO -> Log.i(message.tag, strMessage, message.exception) + LogLevel.WARNING -> Log.w(message.tag, strMessage, message.exception) + LogLevel.ERROR -> Log.e(message.tag, strMessage, message.exception) + LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception) + } + } + + isObtained = false + } +} 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/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/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt index 1c5f221f2efb..4436be7cd7d7 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt @@ -41,16 +41,13 @@ interface ClockProviderPlugin : Plugin, ClockProvider { /** Interface for building clocks and providing information about those clocks */ interface ClockProvider { + /** Initializes the clock provider with debug log buffers */ + fun initialize(buffers: ClockMessageBuffers?) + /** Returns metadata for all clocks this provider knows about */ fun getClocks(): List<ClockMetadata> /** Initializes and returns the target clock design */ - @Deprecated("Use overload with ClockSettings") - fun createClock(id: ClockId): ClockController { - return createClock(ClockSettings(id, null)) - } - - /** Initializes and returns the target clock design */ fun createClock(settings: ClockSettings): ClockController /** A static thumbnail for rendering in some examples */ @@ -98,11 +95,20 @@ interface ClockFaceController { /** Triggers for various animations */ val animations: ClockAnimations - - /** Some clocks may log debug information */ - var messageBuffer: MessageBuffer? } +/** For clocks that want to report debug information */ +data class ClockMessageBuffers( + /** Message buffer for general infra */ + val infraMessageBuffer: MessageBuffer, + + /** Message buffer for small clock renering */ + val smallClockMessageBuffer: MessageBuffer, + + /** Message buffer for large clock rendering */ + val largeClockMessageBuffer: MessageBuffer, +) + /** Specifies layout information for the */ interface ClockFaceLayout { /** All clock views to add to the root constraint layout before applying constraints. */ 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/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 76abad8ae863..bcc20448297d 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -45,12 +45,11 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.Logger import com.android.systemui.log.core.LogLevel.DEBUG -import com.android.systemui.log.dagger.KeyguardLargeClockLog -import com.android.systemui.log.dagger.KeyguardSmallClockLog import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockFaceController +import com.android.systemui.plugins.clocks.ClockMessageBuffers import com.android.systemui.plugins.clocks.ClockTickRate import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.WeatherData @@ -91,117 +90,120 @@ constructor( private val context: Context, @Main private val mainExecutor: DelayableExecutor, @Background private val bgExecutor: Executor, - @KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?, - @KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?, + private val clockBuffers: ClockMessageBuffers, private val featureFlags: FeatureFlags, private val zenModeController: ZenModeController, ) { + var loggers = listOf( + clockBuffers.infraMessageBuffer, + clockBuffers.smallClockMessageBuffer, + clockBuffers.largeClockMessageBuffer + ).map { Logger(it, TAG) } + var clock: ClockController? = null + get() = field set(value) { - smallClockOnAttachStateChangeListener?.let { - field?.smallClock?.view?.removeOnAttachStateChangeListener(it) - smallClockFrame?.viewTreeObserver - ?.removeOnGlobalLayoutListener(onGlobalLayoutListener) - } - largeClockOnAttachStateChangeListener?.let { - field?.largeClock?.view?.removeOnAttachStateChangeListener(it) - } - + disconnectClock(field) field = value - if (value != null) { - smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" }) - value.smallClock.messageBuffer = smallLogBuffer - largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" }) - value.largeClock.messageBuffer = largeLogBuffer - - value.initialize(resources, dozeAmount, 0f) - - if (!regionSamplingEnabled) { - updateColors() - } else { - clock?.let { - smallRegionSampler = createRegionSampler( - it.smallClock.view, - mainExecutor, - bgExecutor, - regionSamplingEnabled, - isLockscreen = true, - ::updateColors - )?.apply { startRegionSampler() } - - largeRegionSampler = createRegionSampler( - it.largeClock.view, - mainExecutor, - bgExecutor, - regionSamplingEnabled, - isLockscreen = true, - ::updateColors - )?.apply { startRegionSampler() } - - updateColors() - } - } - updateFontSizes() - updateTimeListeners() - weatherData?.let { - if (WeatherData.DEBUG) { - Log.i(TAG, "Pushing cached weather data to new clock: $it") - } - value.events.onWeatherDataChanged(it) - } - zenData?.let { - value.events.onZenDataChanged(it) - } - alarmData?.let { - value.events.onAlarmDataChanged(it) - } + connectClock(value) + } - smallClockOnAttachStateChangeListener = - object : OnAttachStateChangeListener { - var pastVisibility: Int? = null - override fun onViewAttachedToWindow(view: View) { - value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) - // Match the asing for view.parent's layout classes. - smallClockFrame = view.parent as ViewGroup - smallClockFrame?.let { frame -> - pastVisibility = frame.visibility - onGlobalLayoutListener = OnGlobalLayoutListener { - val currentVisibility = frame.visibility - if (pastVisibility != currentVisibility) { - pastVisibility = currentVisibility - // when small clock is visible, - // recalculate bounds and sample - if (currentVisibility == View.VISIBLE) { - smallRegionSampler?.stopRegionSampler() - smallRegionSampler?.startRegionSampler() - } - } - } - frame.viewTreeObserver - .addOnGlobalLayoutListener(onGlobalLayoutListener) - } - } + private fun disconnectClock(clock: ClockController?) { + if (clock == null) { return; } + smallClockOnAttachStateChangeListener?.let { + clock.smallClock.view.removeOnAttachStateChangeListener(it) + smallClockFrame?.viewTreeObserver + ?.removeOnGlobalLayoutListener(onGlobalLayoutListener) + } + largeClockOnAttachStateChangeListener?.let { + clock.largeClock.view.removeOnAttachStateChangeListener(it) + } + } - override fun onViewDetachedFromWindow(p0: View) { - smallClockFrame?.viewTreeObserver - ?.removeOnGlobalLayoutListener(onGlobalLayoutListener) - } - } - value.smallClock.view - .addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) + private fun connectClock(clock: ClockController?) { + if (clock == null) { return; } + val clockStr = clock.toString() + loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } } - largeClockOnAttachStateChangeListener = - object : OnAttachStateChangeListener { - override fun onViewAttachedToWindow(p0: View) { - value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) - } - override fun onViewDetachedFromWindow(p0: View) { + clock.initialize(resources, dozeAmount, 0f) + + if (!regionSamplingEnabled) { + updateColors() + } else { + smallRegionSampler = createRegionSampler( + clock.smallClock.view, + mainExecutor, + bgExecutor, + regionSamplingEnabled, + isLockscreen = true, + ::updateColors + ).apply { startRegionSampler() } + + largeRegionSampler = createRegionSampler( + clock.largeClock.view, + mainExecutor, + bgExecutor, + regionSamplingEnabled, + isLockscreen = true, + ::updateColors + ).apply { startRegionSampler() } + + updateColors() + } + updateFontSizes() + updateTimeListeners() + + weatherData?.let { + if (WeatherData.DEBUG) { + Log.i(TAG, "Pushing cached weather data to new clock: $it") + } + clock.events.onWeatherDataChanged(it) + } + zenData?.let { + clock.events.onZenDataChanged(it) + } + alarmData?.let { + clock.events.onAlarmDataChanged(it) + } + + smallClockOnAttachStateChangeListener = object : OnAttachStateChangeListener { + var pastVisibility: Int? = null + override fun onViewAttachedToWindow(view: View) { + clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + // Match the asing for view.parent's layout classes. + smallClockFrame = (view.parent as ViewGroup)?.also { frame -> + pastVisibility = frame.visibility + onGlobalLayoutListener = OnGlobalLayoutListener { + val currentVisibility = frame.visibility + if (pastVisibility != currentVisibility) { + pastVisibility = currentVisibility + // when small clock is visible, + // recalculate bounds and sample + if (currentVisibility == View.VISIBLE) { + smallRegionSampler?.stopRegionSampler() + smallRegionSampler?.startRegionSampler() + } } + } + frame.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener) } - value.largeClock.view - .addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) + } + + override fun onViewDetachedFromWindow(p0: View) { + smallClockFrame?.viewTreeObserver + ?.removeOnGlobalLayoutListener(onGlobalLayoutListener) } } + clock.smallClock.view.addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) + + largeClockOnAttachStateChangeListener = object : OnAttachStateChangeListener { + override fun onViewAttachedToWindow(p0: View) { + clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } + override fun onViewDetachedFromWindow(p0: View) {} + } + clock.largeClock.view.addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) + } @VisibleForTesting var smallClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null @@ -247,6 +249,7 @@ constructor( largeClock.events.onRegionDarknessChanged(isRegionDark) } } + protected open fun createRegionSampler( sampledView: View, mainExecutor: Executor?, @@ -254,7 +257,7 @@ constructor( regionSamplingEnabled: Boolean, isLockscreen: Boolean, updateColors: () -> Unit - ): RegionSampler? { + ): RegionSampler { return RegionSampler( sampledView, mainExecutor, diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index 661ce2ce60ba..878a5d88f164 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -28,9 +28,8 @@ 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.log.LogBuffer; -import com.android.systemui.log.dagger.KeyguardClockLog; import com.android.systemui.plugins.PluginManager; +import com.android.systemui.plugins.clocks.ClockMessageBuffers; import com.android.systemui.res.R; import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.shared.clocks.DefaultClockProvider; @@ -56,7 +55,7 @@ public abstract class ClockRegistryModule { FeatureFlags featureFlags, @Main Resources resources, LayoutInflater layoutInflater, - @KeyguardClockLog LogBuffer logBuffer) { + ClockMessageBuffers clockBuffers) { ClockRegistry registry = new ClockRegistry( context, pluginManager, @@ -72,7 +71,7 @@ public abstract class ClockRegistryModule { featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION), migrateClocksToBlueprint()), context.getString(R.string.lockscreen_clock_id_fallback), - logBuffer, + clockBuffers, /* keepAllLoaded = */ false, /* subTag = */ "System", /* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK)); 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/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/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/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/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/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/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/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/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/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index d8bb3e65392f..0d5ba641b599 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -26,6 +26,7 @@ import com.android.systemui.log.echo.LogcatEchoTrackerDebug; import com.android.systemui.log.echo.LogcatEchoTrackerProd; import com.android.systemui.log.table.TableLogBuffer; import com.android.systemui.log.table.TableLogBufferFactory; +import com.android.systemui.plugins.clocks.ClockMessageBuffers; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository; import com.android.systemui.qs.pipeline.shared.TileSpec; @@ -417,6 +418,18 @@ public class LogModule { } /** + * Provides a {@link ClockMessageBuffers} which contains the keyguard clock message buffers. + */ + @Provides + public static ClockMessageBuffers provideKeyguardClockMessageBuffers( + @KeyguardClockLog LogBuffer infraClockLog, + @KeyguardSmallClockLog LogBuffer smallClockLog, + @KeyguardLargeClockLog LogBuffer largeClockLog + ) { + return new ClockMessageBuffers(infraClockLog, smallClockLog, largeClockLog); + } + + /** * Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}. */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java index 2345667f0409..83b6f0d40aff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java @@ -19,16 +19,21 @@ import android.os.IBinder; import android.service.quicksettings.IQSTileService; import android.util.Log; +import androidx.annotation.NonNull; + public class QSTileServiceWrapper { private static final String TAG = "IQSTileServiceWrapper"; + @NonNull private final IQSTileService mService; - public QSTileServiceWrapper(IQSTileService service) { + public QSTileServiceWrapper(@NonNull IQSTileService service) { mService = service; } + // This can never be null, as it's the constructor parameter and it's final + @NonNull public IBinder asBinder() { return mService.asBinder(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index e08eb37b79ba..880289e88813 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -40,6 +40,7 @@ import android.text.format.DateUtils; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; @@ -54,8 +55,10 @@ import dagger.assisted.AssistedInject; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; /** * Manages the lifecycle of a TileService. @@ -101,8 +104,8 @@ public class TileLifecycleManager extends BroadcastReceiver implements private final ActivityManager mActivityManager; private Set<Integer> mQueuedMessages = new ArraySet<>(); - @Nullable - private volatile QSTileServiceWrapper mWrapper; + @NonNull + private volatile Optional<QSTileServiceWrapper> mOptionalWrapper = Optional.empty(); private boolean mListening; private IBinder mClickBinder; @@ -222,6 +225,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements // Only try a new binding if we are not currently bound. mIsBound.compareAndSet(false, bindServices()); if (!mIsBound.get()) { + Log.d(TAG, "Failed to bind to service"); mContext.unbindService(this); } } catch (SecurityException e) { @@ -281,7 +285,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements service.linkToDeath(this, 0); } catch (RemoteException e) { } - mWrapper = wrapper; + mOptionalWrapper = Optional.of(wrapper); handlePendingMessages(); } @@ -368,6 +372,10 @@ public class TileLifecycleManager extends BroadcastReceiver implements * are supposed to be bound, we will try to bind after some amount of time. */ private void handleDeath() { + if (!mIsBound.get()) { + // If we are already not bound, don't do anything else. + return; + } mExecutor.execute(() -> { if (!mIsBound.get()) { // If we are already not bound, don't do anything else. @@ -522,7 +530,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements @Override public void onTileAdded() { if (mDebug) Log.d(TAG, "onTileAdded " + getComponent()); - if (mWrapper == null || !mWrapper.onTileAdded()) { + if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onTileAdded)) { queueMessage(MSG_ON_ADDED); handleDeath(); } @@ -531,7 +539,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements @Override public void onTileRemoved() { if (mDebug) Log.d(TAG, "onTileRemoved " + getComponent()); - if (mWrapper == null || !mWrapper.onTileRemoved()) { + if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onTileRemoved)) { queueMessage(MSG_ON_REMOVED); handleDeath(); } @@ -541,7 +549,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements public void onStartListening() { if (mDebug) Log.d(TAG, "onStartListening " + getComponent()); mListening = true; - if (mWrapper != null && !mWrapper.onStartListening()) { + if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStartListening)) { handleDeath(); } } @@ -550,7 +558,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements public void onStopListening() { if (mDebug) Log.d(TAG, "onStopListening " + getComponent()); mListening = false; - if (mWrapper != null && !mWrapper.onStopListening()) { + if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) { handleDeath(); } } @@ -558,7 +566,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements @Override public void onClick(IBinder iBinder) { if (mDebug) Log.d(TAG, "onClick " + iBinder + " " + getComponent() + " " + mUser); - if (mWrapper == null || !mWrapper.onClick(iBinder)) { + if (isNullOrFailedAction(mOptionalWrapper, (wrapper) -> wrapper.onClick(iBinder))) { mClickBinder = iBinder; queueMessage(MSG_ON_CLICK); handleDeath(); @@ -568,7 +576,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements @Override public void onUnlockComplete() { if (mDebug) Log.d(TAG, "onUnlockComplete " + getComponent()); - if (mWrapper == null || !mWrapper.onUnlockComplete()) { + if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onUnlockComplete)) { queueMessage(MSG_ON_UNLOCK_COMPLETE); handleDeath(); } @@ -577,7 +585,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements @Nullable @Override public IBinder asBinder() { - return mWrapper != null ? mWrapper.asBinder() : null; + return mOptionalWrapper.map(QSTileServiceWrapper::asBinder).orElse(null); } @Override @@ -591,18 +599,42 @@ public class TileLifecycleManager extends BroadcastReceiver implements } private void freeWrapper() { - if (mWrapper != null) { + if (mOptionalWrapper.isPresent()) { try { - mWrapper.asBinder().unlinkToDeath(this, 0); + mOptionalWrapper.ifPresent( + (wrapper) -> wrapper.asBinder().unlinkToDeath(this, 0) + ); } catch (NoSuchElementException e) { Log.w(TAG, "Trying to unlink not linked recipient for component" + mIntent.getComponent().flattenToShortString()); } - mWrapper = null; + mOptionalWrapper = Optional.empty(); } } public interface TileChangeListener { void onTileChanged(ComponentName tile); } + + /** + * Returns true if the Optional is empty OR performing the action on the content of the Optional + * (when not empty) fails. + */ + private static boolean isNullOrFailedAction( + Optional<QSTileServiceWrapper> optionalWrapper, + Predicate<QSTileServiceWrapper> action + ) { + return !optionalWrapper.map(action::test).orElse(false); + } + + /** + * Returns true if the Optional is not empty AND performing the action on the content of + * the Optional fails. + */ + private static boolean isNotNullAndFailedAction( + Optional<QSTileServiceWrapper> optionalWrapper, + Predicate<QSTileServiceWrapper> action + ) { + return !optionalWrapper.map(action::test).orElse(true); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt index 66da8bdd0311..216d716b07a4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt @@ -42,9 +42,7 @@ import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.recordissue.RecordIssueDialogDelegate import com.android.systemui.res.R -import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil -import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject @@ -63,8 +61,7 @@ constructor( private val keyguardDismissUtil: KeyguardDismissUtil, private val keyguardStateController: KeyguardStateController, private val dialogLaunchAnimator: DialogLaunchAnimator, - private val sysuiDialogFactory: SystemUIDialog.Factory, - private val userContextProvider: UserContextProvider, + private val delegateFactory: RecordIssueDialogDelegate.Factory, ) : QSTileImpl<QSTile.BooleanState>( host, @@ -102,7 +99,8 @@ constructor( private fun showPrompt(view: View?) { val dialog: AlertDialog = - RecordIssueDialogDelegate(sysuiDialogFactory, userContextProvider) { + delegateFactory + .create { isRecording = true refreshState() } diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt index 5bf44fa5b143..e051df4e6c7b 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt @@ -24,27 +24,63 @@ import android.content.Context import android.content.res.ColorStateList import android.graphics.Color import android.os.Bundle +import android.os.UserHandle import android.view.Gravity import android.view.LayoutInflater import android.view.WindowManager import android.widget.Button import android.widget.PopupMenu import android.widget.Switch +import androidx.annotation.AnyThread +import androidx.annotation.MainThread +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger +import com.android.systemui.mediaprojection.SessionCreationSource +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog +import com.android.systemui.qs.tiles.RecordIssueTile import com.android.systemui.res.R import com.android.systemui.screenrecord.RecordingService import com.android.systemui.screenrecord.ScreenRecordingAudioSource import com.android.systemui.settings.UserContextProvider +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.SystemUIDialog +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.concurrent.Executor -class RecordIssueDialogDelegate( +class RecordIssueDialogDelegate +@AssistedInject +constructor( private val factory: SystemUIDialog.Factory, private val userContextProvider: UserContextProvider, - private val onStarted: Runnable + private val userTracker: UserTracker, + private val flags: FeatureFlagsClassic, + @Background private val bgExecutor: Executor, + @Main private val mainExecutor: Executor, + private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>, + private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, + private val userFileManager: UserFileManager, + @Assisted private val onStarted: Runnable, ) : SystemUIDialog.Delegate { + /** To inject dependencies and allow for easier testing */ + @AssistedFactory + interface Factory { + /** Create a dialog object */ + fun create(onStarted: Runnable): RecordIssueDialogDelegate + } + @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch private lateinit var issueTypeButton: Button + @MainThread override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { dialog.apply { setView(LayoutInflater.from(context).inflate(R.layout.record_issue_dialog, null)) @@ -63,17 +99,64 @@ class RecordIssueDialogDelegate( override fun createDialog(): SystemUIDialog = factory.create(this) + @MainThread override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { dialog.apply { window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) window?.setGravity(Gravity.CENTER) screenRecordSwitch = requireViewById(R.id.screenrecord_switch) + screenRecordSwitch.setOnCheckedChangeListener { _, isEnabled -> + onScreenRecordSwitchClicked(context, isEnabled) + } issueTypeButton = requireViewById(R.id.issue_type_button) issueTypeButton.setOnClickListener { onIssueTypeClicked(context) } } } + @AnyThread + private fun onScreenRecordSwitchClicked(context: Context, isEnabled: Boolean) { + if (!isEnabled) return + + bgExecutor.execute { + if ( + flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) && + devicePolicyResolver + .get() + .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId)) + ) { + mainExecutor.execute { + ScreenCaptureDisabledDialog(context).show() + screenRecordSwitch.isChecked = false + } + return@execute + } + + mediaProjectionMetricsLogger.notifyProjectionInitiated( + userTracker.userId, + SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER + ) + + if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) { + val prefs = + userFileManager.getSharedPreferences( + RecordIssueTile.TILE_SPEC, + Context.MODE_PRIVATE, + userTracker.userId + ) + if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) { + mainExecutor.execute { + ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply { + setOnCancelListener { screenRecordSwitch.isChecked = false } + show() + } + } + } + } + } + } + + @MainThread private fun onIssueTypeClicked(context: Context) { val selectedCategory = issueTypeButton.text.toString() val popupMenu = PopupMenu(context, issueTypeButton) diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt new file mode 100644 index 000000000000..de6d3f698285 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt @@ -0,0 +1,49 @@ +/* + * 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.recordissue + +import android.content.SharedPreferences +import android.os.Bundle +import android.view.Gravity +import android.view.WindowManager +import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.SystemUIDialog + +const val HAS_APPROVED_SCREEN_RECORDING = "HasApprovedScreenRecord" + +class ScreenCapturePermissionDialogDelegate( + private val dialogFactory: SystemUIDialog.Factory, + private val sharedPreferences: SharedPreferences, +) : SystemUIDialog.Delegate { + + override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + dialog.apply { + setIcon(R.drawable.ic_screenrecord) + setTitle(R.string.screenrecord_permission_dialog_title) + setMessage(R.string.screenrecord_permission_dialog_warning_entire_screen) + setNegativeButton(R.string.slice_permission_deny) { _, _ -> cancel() } + setPositiveButton(R.string.slice_permission_allow) { _, _ -> + sharedPreferences.edit().putBoolean(HAS_APPROVED_SCREEN_RECORDING, true).apply() + dismiss() + } + window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) + window?.setGravity(Gravity.CENTER) + } + } + + override fun createDialog(): SystemUIDialog = dialogFactory.create(this) +} 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/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/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/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/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/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 62fdc294e2d6..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 @@ -993,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()); @@ -1129,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) { @@ -1769,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; @@ -1820,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 @@ -2364,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(); @@ -2886,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) { @@ -3035,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(); @@ -3126,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); } @@ -3196,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); } } @@ -3394,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; @@ -3437,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); @@ -4122,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); } } @@ -4137,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())" @@ -4223,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); } } @@ -4263,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()) { @@ -4323,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()) { @@ -5208,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); } }); @@ -5292,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; } @@ -5319,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)) { @@ -5341,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); } @@ -5983,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) { @@ -6270,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; } @@ -6281,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; } @@ -6377,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 7c7d9431e0fc..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) { @@ -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 c4e6b909d023..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 @@ -254,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 { @@ -289,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(); } } @@ -401,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(); @@ -533,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; } @@ -740,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; } @@ -785,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; } @@ -940,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/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 2740cc6682a6..11456ffaa4f3 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 @@ -453,11 +453,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public void initNotificationIconArea() { ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area); if (NotificationIconContainerRefactor.isEnabled()) { - mNotificationIconAreaInner = - LayoutInflater.from(getContext()) - .inflate(R.layout.notification_icon_area, notificationIconArea, true); + LayoutInflater.from(getContext()) + .inflate(R.layout.notification_icon_area, notificationIconArea, true); NotificationIconContainer notificationIcons = notificationIconArea.requireViewById(R.id.notificationIcons); + mNotificationIconAreaInner = notificationIcons; mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons); } else { mNotificationIconAreaInner = diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 6f58bc2fcc9f..e6637e60b146 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -30,13 +30,15 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac 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.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogcatOnlyMessageBuffer import com.android.systemui.plugins.clocks.ClockAnimations import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockEvents import com.android.systemui.plugins.clocks.ClockFaceConfig import com.android.systemui.plugins.clocks.ClockFaceController import com.android.systemui.plugins.clocks.ClockFaceEvents +import com.android.systemui.plugins.clocks.ClockMessageBuffers import com.android.systemui.plugins.clocks.ClockTickRate import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController @@ -94,9 +96,9 @@ class ClockEventControllerTest : SysuiTestCase() { @Mock private lateinit var largeClockEvents: ClockFaceEvents @Mock private lateinit var parentView: View private lateinit var repository: FakeKeyguardRepository - @Mock private lateinit var smallLogBuffer: LogBuffer - @Mock private lateinit var largeLogBuffer: LogBuffer @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor + private val messageBuffer = LogcatOnlyMessageBuffer(LogLevel.DEBUG) + private val clockBuffers = ClockMessageBuffers(messageBuffer, messageBuffer, messageBuffer) private lateinit var underTest: ClockEventController @Mock private lateinit var zenModeController: ZenModeController @@ -140,8 +142,7 @@ class ClockEventControllerTest : SysuiTestCase() { context, mainExecutor, bgExecutor, - smallLogBuffer, - largeLogBuffer, + clockBuffers, withDeps.featureFlags, zenModeController ) 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/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/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/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt index 9b61447c8de7..c7479fd50db1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt @@ -30,8 +30,8 @@ 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.recordissue.RecordIssueDialogDelegate import com.android.systemui.res.R -import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController @@ -65,9 +65,9 @@ class RecordIssueTileTest : SysuiTestCase() { @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator - @Mock private lateinit var dialogFactory: SystemUIDialog.Factory + @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory + @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate @Mock private lateinit var dialog: SystemUIDialog - @Mock private lateinit var userContextProvider: UserContextProvider private lateinit var testableLooper: TestableLooper private lateinit var tile: RecordIssueTile @@ -76,7 +76,8 @@ class RecordIssueTileTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) whenever(host.context).thenReturn(mContext) - whenever(dialogFactory.create(any())).thenReturn(dialog) + whenever(delegateFactory.create(any())).thenReturn(dialogDelegate) + whenever(dialogDelegate.createDialog()).thenReturn(dialog) testableLooper = TestableLooper.get(this) tile = @@ -93,8 +94,7 @@ class RecordIssueTileTest : SysuiTestCase() { keyguardDismissUtil, keyguardStateController, dialogLauncherAnimator, - dialogFactory, - userContextProvider, + delegateFactory, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt index c5d35245ba7c..7ce51aea90e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt @@ -17,6 +17,9 @@ package com.android.systemui.recordissue import android.app.Dialog +import android.content.Context +import android.content.SharedPreferences +import android.os.UserHandle import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.widget.Button @@ -25,48 +28,107 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger +import com.android.systemui.mediaprojection.SessionCreationSource +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver import com.android.systemui.model.SysUiState +import com.android.systemui.qs.tiles.RecordIssueTile import com.android.systemui.res.R +import com.android.systemui.settings.UserContextProvider +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialogManager -import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executor import java.util.concurrent.TimeUnit import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class RecordIssueDialogDelegateTest : SysuiTestCase() { + @Mock private lateinit var flags: FeatureFlagsClassic + @Mock private lateinit var devicePolicyResolver: ScreenCaptureDevicePolicyResolver + @Mock private lateinit var dprLazy: dagger.Lazy<ScreenCaptureDevicePolicyResolver> + @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger + @Mock private lateinit var userContextProvider: UserContextProvider + @Mock private lateinit var userTracker: UserTracker + @Mock private lateinit var userFileManager: UserFileManager + @Mock private lateinit var sharedPreferences: SharedPreferences + + @Mock private lateinit var sysuiState: SysUiState + @Mock private lateinit var systemUIDialogManager: SystemUIDialogManager + @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var bgExecutor: Executor + @Mock private lateinit var mainExecutor: Executor + @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + private lateinit var dialog: SystemUIDialog + private lateinit var factory: SystemUIDialog.Factory private lateinit var latch: CountDownLatch @Before fun setup() { - val dialogFactory = - SystemUIDialog.Factory( - context, - mock<FeatureFlags>(), - mock<SystemUIDialogManager>(), - mock<SysUiState>().apply { - whenever(setFlag(anyInt(), anyBoolean())).thenReturn(this) - }, - mock<BroadcastDispatcher>(), - mock<DialogLaunchAnimator>() + MockitoAnnotations.initMocks(this) + whenever(dprLazy.get()).thenReturn(devicePolicyResolver) + whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState) + whenever(userContextProvider.userContext).thenReturn(mContext) + whenever( + userFileManager.getSharedPreferences( + eq(RecordIssueTile.TILE_SPEC), + eq(Context.MODE_PRIVATE), + anyInt() + ) + ) + .thenReturn(sharedPreferences) + + factory = + spy( + SystemUIDialog.Factory( + context, + flags, + systemUIDialogManager, + sysuiState, + broadcastDispatcher, + dialogLaunchAnimator + ) ) latch = CountDownLatch(1) dialog = - RecordIssueDialogDelegate(dialogFactory, mock()) { latch.countDown() }.createDialog() + RecordIssueDialogDelegate( + factory, + userContextProvider, + userTracker, + flags, + bgExecutor, + mainExecutor, + dprLazy, + mediaProjectionMetricsLogger, + userFileManager, + ) { + latch.countDown() + } + .createDialog() dialog.show() } @@ -91,4 +153,82 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { dialog.getButton(Dialog.BUTTON_POSITIVE).callOnClick() latch.await(1L, TimeUnit.MILLISECONDS) } + + @Test + fun screenCaptureDisabledDialog_isShown_whenFunctionalityIsDisabled() { + whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) + .thenReturn(true) + whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>())) + .thenReturn(true) + + val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch) + screenRecordSwitch.isChecked = true + + val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java) + verify(bgExecutor).execute(bgCaptor.capture()) + bgCaptor.value.run() + + val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java) + verify(mainExecutor).execute(mainCaptor.capture()) + mainCaptor.value.run() + + verify(mediaProjectionMetricsLogger, never()) + .notifyProjectionInitiated( + anyInt(), + eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER) + ) + assertThat(screenRecordSwitch.isChecked).isFalse() + } + + @Test + fun screenCapturePermissionDialog_isShown_correctly() { + whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) + .thenReturn(false) + whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>())) + .thenReturn(false) + whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true) + whenever(sharedPreferences.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) + .thenReturn(false) + + val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch) + screenRecordSwitch.isChecked = true + + val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java) + verify(bgExecutor).execute(bgCaptor.capture()) + bgCaptor.value.run() + + val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java) + verify(mainExecutor).execute(mainCaptor.capture()) + mainCaptor.value.run() + + verify(mediaProjectionMetricsLogger) + .notifyProjectionInitiated( + anyInt(), + eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER) + ) + verify(factory).create(any<ScreenCapturePermissionDialogDelegate>()) + } + + @Test + fun noDialogsAreShown_forScreenRecord_whenApprovalIsAlreadyGiven() { + whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) + .thenReturn(false) + whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>())) + .thenReturn(false) + whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(false) + + val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch) + screenRecordSwitch.isChecked = true + + val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java) + verify(bgExecutor).execute(bgCaptor.capture()) + bgCaptor.value.run() + + verify(mediaProjectionMetricsLogger) + .notifyProjectionInitiated( + anyInt(), + eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER) + ) + verify(factory, never()).create(any<ScreenCapturePermissionDialogDelegate>()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index ee94cbbcfd79..ee27c5c9ba0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.TRANSIT_CLOCK import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockId +import com.android.systemui.plugins.clocks.ClockMessageBuffers import com.android.systemui.plugins.clocks.ClockMetadata import com.android.systemui.plugins.clocks.ClockProviderPlugin import com.android.systemui.plugins.clocks.ClockSettings @@ -128,6 +129,7 @@ class ClockRegistryTest : SysuiTestCase() { override fun createClock(settings: ClockSettings): ClockController = createCallbacks[settings.clockId!!]!!(settings.clockId!!) override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id) + override fun initialize(buffers: ClockMessageBuffers?) { } fun addClock( id: ClockId, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt index fef262f50306..e0e8d1f51884 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt @@ -26,6 +26,7 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.customization.R +import com.android.systemui.plugins.clocks.ClockId import com.android.systemui.plugins.clocks.ClockSettings import com.android.systemui.shared.clocks.DefaultClockController.Companion.DOZE_COLOR import com.android.systemui.util.mockito.any @@ -49,6 +50,9 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +private fun DefaultClockProvider.createClock(id: ClockId): DefaultClockController = + createClock(ClockSettings(id, null)) as DefaultClockController + @RunWith(AndroidTestingRunner::class) @SmallTest class DefaultClockProviderTest : SysuiTestCase() { 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/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 14751c2dc29e..54d3607c7a83 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 @@ -36,6 +36,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import android.view.LayoutInflater; import android.view.View; import androidx.test.filters.SmallTest; @@ -91,7 +92,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private NotificationIconAreaController mMockNotificationAreaController; private ShadeExpansionStateManager mShadeExpansionStateManager; - private View mNotificationAreaInner; private OngoingCallController mOngoingCallController; private SystemStatusAnimationScheduler mAnimationScheduler; private StatusBarLocationPublisher mLocationPublisher; @@ -270,15 +270,15 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); - assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); - assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); } @Test @@ -310,7 +310,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are hidden assertEquals(View.INVISIBLE, getClockView().getVisibility()); - assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } @@ -326,7 +326,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are shown assertEquals(View.VISIBLE, getClockView().getVisibility()); - assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } @@ -343,7 +343,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are hidden assertEquals(View.INVISIBLE, getClockView().getVisibility()); - assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); // WHEN the shade is updated to no longer be open @@ -354,7 +354,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are shown assertEquals(View.VISIBLE, getClockView().getVisibility()); - assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } @@ -368,7 +368,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are shown assertEquals(View.VISIBLE, getClockView().getVisibility()); - assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } @@ -382,7 +382,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are hidden assertEquals(View.GONE, getClockView().getVisibility()); - assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } @@ -396,7 +396,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are hidden assertEquals(View.GONE, getClockView().getVisibility()); - assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); // WHEN the transition has finished @@ -405,7 +405,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are shown assertEquals(View.VISIBLE, getClockView().getVisibility()); - assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } @@ -438,7 +438,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); - assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); } @Test @@ -503,8 +503,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, 0, 0, true); // Notification area is hidden without delay - assertEquals(0f, mNotificationAreaInner.getAlpha(), 0.01); - assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility()); + assertEquals(0f, getNotificationAreaView().getAlpha(), 0.01); + assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); } @Test @@ -723,11 +723,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private void setUpNotificationIconAreaController() { mMockNotificationAreaController = mock(NotificationIconAreaController.class); - - mNotificationAreaInner = new View(mContext); - - when(mMockNotificationAreaController.getNotificationInnerAreaView()).thenReturn( - mNotificationAreaInner); + View notificationAreaInner = + LayoutInflater.from(mContext).inflate(R.layout.notification_icon_area, null); + when(mMockNotificationAreaController.getNotificationInnerAreaView()) + .thenReturn(notificationAreaInner); } /** @@ -782,4 +781,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private View getEndSideContentView() { return mFragment.getView().findViewById(R.id.status_bar_end_side_content); } + + private View getNotificationAreaView() { + return mFragment.getView().findViewById(R.id.notificationIcons); + } } 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/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/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java index d9741c8e867d..4a6d5c9bc65e 100644 --- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java +++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java @@ -25,9 +25,6 @@ import android.service.autofill.FillRequest; import android.service.autofill.FillResponse; import android.util.Slog; -import java.util.Objects; - - /** * Requests autofill response from a Remote Autofill Service. This autofill service can be * either a Credential Autofill Service or the user-opted autofill service. @@ -51,7 +48,6 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal private final RemoteFillService mRemoteFillService; private final SecondaryProviderCallback mCallback; - private FillRequest mLastFillRequest; private int mLastFlag; SecondaryProviderHandler( @@ -97,17 +93,11 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal } /** - * Requests a new fill response. If the fill request is same as the last requested fill request, - * then the request is duped. + * Requests a new fill response. */ public void onFillRequest(FillRequest pendingFillRequest, int flag) { - if (Objects.equals(pendingFillRequest, mLastFillRequest)) { - Slog.v(TAG, "Deduping fill request to secondary provider."); - return; - } Slog.v(TAG, "Requesting fill response to secondary provider."); mLastFlag = flag; - mLastFillRequest = pendingFillRequest; mRemoteFillService.onFillRequest(pendingFillRequest); } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index d527ce0e1b2a..c4e8f12b72f5 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -367,6 +367,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private SparseArray<FillResponse> mResponses; + @GuardedBy("mLock") + private SparseArray<FillResponse> mSecondaryResponses; + /** * Contexts read from the app; they will be updated (sanitized, change values for save) before * sent to {@link AutofillService}. Ordered by the time they were read. @@ -713,7 +716,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPendingFillRequest.getDelayedFillIntentSender()); } mLastFillRequest = mPendingFillRequest; - mRemoteFillService.onFillRequest(mPendingFillRequest); + if (shouldRequestSecondaryProvider(mPendingFillRequest.getFlags()) + && mSecondaryProviderHandler != null) { + Slog.v(TAG, "Requesting fill response to secondary provider."); + mSecondaryProviderHandler.onFillRequest(mPendingFillRequest, + mPendingFillRequest.getFlags()); + } else if (mRemoteFillService != null) { + mRemoteFillService.onFillRequest(mPendingFillRequest); + } mPendingInlineSuggestionsRequest = null; mWaitForInlineRequest = false; mPendingFillRequest = null; @@ -1196,7 +1206,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState, int flags) { - final FillResponse existingResponse = viewState.getResponse(); + final FillResponse existingResponse = shouldRequestSecondaryProvider(flags) + ? viewState.getSecondaryResponse() : viewState.getResponse(); mFillRequestEventLogger.startLogForNewRequest(); mRequestCount++; mFillRequestEventLogger.maybeSetAppPackageUid(uid); @@ -1804,6 +1815,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } synchronized (mLock) { + if (mSecondaryResponses == null) { + mSecondaryResponses = new SparseArray<>(2); + } + mSecondaryResponses.put(fillResponse.getRequestId(), fillResponse); setViewStatesLocked(fillResponse, ViewState.STATE_FILLABLE, /* clearResponse= */ false, /* isPrimary= */ false); @@ -3980,7 +3995,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // If it's not, then check if it should start a partition. - if (shouldStartNewPartitionLocked(id)) { + if (shouldStartNewPartitionLocked(id, flags)) { if (sDebug) { Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": " + viewState.getStateAsString()); @@ -4008,9 +4023,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @return {@code true} if a new partition should be started */ @GuardedBy("mLock") - private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) { + private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id, int flags) { final ViewState currentView = mViewStates.get(id); - if (mResponses == null) { + SparseArray<FillResponse> responses = shouldRequestSecondaryProvider(flags) + ? mSecondaryResponses : mResponses; + if (responses == null) { return currentView != null && (currentView.getState() & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) == 0; } @@ -4022,7 +4039,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return true; } - final int numResponses = mResponses.size(); + final int numResponses = responses.size(); if (numResponses >= AutofillManagerService.getPartitionMaxCount()) { Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id + " reached maximum of " + AutofillManagerService.getPartitionMaxCount()); @@ -4030,7 +4047,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } for (int responseNum = 0; responseNum < numResponses; responseNum++) { - final FillResponse response = mResponses.valueAt(responseNum); + final FillResponse response = responses.valueAt(responseNum); if (ArrayUtils.contains(response.getIgnoredIds(), id)) { return false; @@ -4066,6 +4083,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } boolean shouldRequestSecondaryProvider(int flags) { + if (!mService.isAutofillCredmanIntegrationEnabled() + || mSecondaryProviderHandler == null) { + return false; + } if (mIsPrimaryCredential) { return (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) == 0; } else { @@ -4205,12 +4226,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } break; case ACTION_VIEW_ENTERED: - if (shouldRequestSecondaryProvider(flags) - && mSecondaryProviderHandler != null - && mAssistReceiver.mLastFillRequest != null) { - mSecondaryProviderHandler.onFillRequest(mAssistReceiver.mLastFillRequest, - flags); - } mLatencyBaseTime = SystemClock.elapsedRealtime(); boolean wasPreviouslyFillDialog = mPreviouslyFillDialogPotentiallyStarted; mPreviouslyFillDialogPotentiallyStarted = false; @@ -4225,6 +4240,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState viewState.setCurrentValue(value); } + if (shouldRequestSecondaryProvider(flags)) { + if (requestNewFillResponseOnViewEnteredIfNecessaryLocked( + id, viewState, flags)) { + Slog.v(TAG, "Started a new fill request for secondary provider."); + return; + } + // If the ViewState is ready to be displayed, onReady() will be called. + viewState.update(value, virtualBounds, flags); + + // return here because primary provider logic is not applicable. + return; + } + if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { if (sDebug) Slog.d(TAG, "Ignoring VIEW_ENTERED on URL BAR (id=" + id + ")"); return; diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java index b0bb9ec66f46..fec5aa531cdd 100644 --- a/services/autofill/java/com/android/server/autofill/ViewState.java +++ b/services/autofill/java/com/android/server/autofill/ViewState.java @@ -162,6 +162,11 @@ final class ViewState { return mPrimaryFillResponse; } + @Nullable + FillResponse getSecondaryResponse() { + return mSecondaryFillResponse; + } + void setResponse(FillResponse response) { setResponse(response, /* isPrimary= */ true); } 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/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 badd7f085e56..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); @@ -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/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 2314bb772494..2d6177d9d27b 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 @@ -399,7 +403,8 @@ public class AutomaticBrightnessController { | (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0) | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE ? BrightnessEvent.FLAG_DOZE_SCALE : 0) - | (isInIdleMode() ? BrightnessEvent.FLAG_IDLE_CURVE : 0)); + | (getMode() == AUTO_BRIGHTNESS_MODE_IDLE + ? BrightnessEvent.FLAG_IDLE_CURVE : 0)); } if (!mAmbientLuxValid) { @@ -510,7 +515,8 @@ public class AutomaticBrightnessController { if (mLoggingEnabled) { Slog.d(TAG, "Display policy transitioning from " + oldPolicy + " to " + policy); } - if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy) && !isInIdleMode()) { + if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy) + && getMode() != AUTO_BRIGHTNESS_MODE_IDLE) { mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL, mCurrentBrightnessMapper.getShortTermModelTimeout()); } else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) { @@ -551,7 +557,7 @@ public class AutomaticBrightnessController { boolean shouldResetShortTermModel) { if (mBrightnessMappingStrategyMap.get(AUTO_BRIGHTNESS_MODE_DEFAULT) .setBrightnessConfiguration(configuration)) { - if (!isInIdleMode() && shouldResetShortTermModel) { + if (getMode() != AUTO_BRIGHTNESS_MODE_IDLE && shouldResetShortTermModel) { resetShortTermModel(); } return true; @@ -559,8 +565,9 @@ public class AutomaticBrightnessController { return false; } - public boolean isInIdleMode() { - return mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE; + @AutomaticBrightnessController.AutomaticBrightnessMode + public int getMode() { + return mCurrentBrightnessMapper.getMode(); } public void dump(PrintWriter pw) { @@ -616,12 +623,12 @@ 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(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()); } @@ -738,7 +745,7 @@ public class AutomaticBrightnessController { lux = 0; } mAmbientLux = lux; - if (isInIdleMode()) { + if (getMode() == AUTO_BRIGHTNESS_MODE_IDLE) { mAmbientBrighteningThreshold = mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux); mAmbientDarkeningThreshold = @@ -830,7 +837,7 @@ public class AutomaticBrightnessController { } earliestValidTime = mAmbientLightRingBuffer.getTime(i); } - return earliestValidTime + (isInIdleMode() + return earliestValidTime + (getMode() == AUTO_BRIGHTNESS_MODE_IDLE ? mBrighteningLightDebounceConfigIdle : mBrighteningLightDebounceConfig); } @@ -843,7 +850,7 @@ public class AutomaticBrightnessController { } earliestValidTime = mAmbientLightRingBuffer.getTime(i); } - return earliestValidTime + (isInIdleMode() + return earliestValidTime + (getMode() == AUTO_BRIGHTNESS_MODE_IDLE ? mDarkeningLightDebounceConfigIdle : mDarkeningLightDebounceConfig); } @@ -968,7 +975,7 @@ public class AutomaticBrightnessController { mPreThresholdBrightness = mScreenAutoBrightness; } mScreenAutoBrightness = newScreenAutoBrightness; - if (isInIdleMode()) { + if (getMode() == AUTO_BRIGHTNESS_MODE_IDLE) { mScreenBrighteningThreshold = clampScreenBrightness( mScreenBrightnessThresholdsIdle.getBrighteningThreshold( newScreenAutoBrightness)); @@ -1224,14 +1231,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 f2ffd4d3f1d9..8405e0a52084 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -19,15 +19,18 @@ 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; +import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.res.Resources; import android.content.res.TypedArray; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.BrightnessCorrection; import android.os.PowerManager; +import android.os.UserHandle; +import android.provider.Settings; import android.util.LongArray; import android.util.MathUtils; import android.util.Pair; @@ -79,41 +82,50 @@ public abstract class BrightnessMappingStrategy { * Creates a BrightnessMapping strategy. We do not create a simple mapping strategy for idle * mode. * - * @param resources + * @param context * @param displayDeviceConfig * @param mode The auto-brightness mode. Different modes use different brightness curves * @param displayWhiteBalanceController * @return the BrightnessMappingStrategy */ @Nullable - static BrightnessMappingStrategy create(Resources resources, + static BrightnessMappingStrategy create(Context context, DisplayDeviceConfig displayDeviceConfig, @AutomaticBrightnessController.AutomaticBrightnessMode int mode, - DisplayWhiteBalanceController displayWhiteBalanceController) { + @Nullable DisplayWhiteBalanceController displayWhiteBalanceController) { // Display independent, mode dependent values float[] brightnessLevelsNits = null; float[] brightnessLevels = null; float[] luxLevels = null; + int preset = Settings.System.getIntForUser(context.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_FOR_ALS, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT); switch (mode) { case AUTO_BRIGHTNESS_MODE_DEFAULT -> { brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(); - luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(); - brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels(); + luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset); + brightnessLevels = + displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset); } case AUTO_BRIGHTNESS_MODE_IDLE -> { - brightnessLevelsNits = getFloatArray(resources.obtainTypedArray( + brightnessLevelsNits = getFloatArray(context.getResources().obtainTypedArray( com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle)); - luxLevels = getLuxLevels(resources.getIntArray( + luxLevels = getLuxLevels(context.getResources().getIntArray( com.android.internal.R.array.config_autoBrightnessLevelsIdle)); } + case AUTO_BRIGHTNESS_MODE_DOZE -> { + luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset); + brightnessLevels = + displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset); + } } // Display independent, mode independent values - float autoBrightnessAdjustmentMaxGamma = resources.getFraction( + float autoBrightnessAdjustmentMaxGamma = context.getResources().getFraction( com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1); - long shortTermModelTimeout = resources.getInteger( + long shortTermModelTimeout = context.getResources().getInteger( com.android.internal.R.integer.config_autoBrightnessShortTermModelTimeout); // Display dependent values - used for physical mapping strategy nits -> brightness @@ -818,6 +830,8 @@ public abstract class BrightnessMappingStrategy { private float mAutoBrightnessAdjustment; private float mUserLux; private float mUserBrightness; + + @Nullable private final DisplayWhiteBalanceController mDisplayWhiteBalanceController; @AutomaticBrightnessController.AutomaticBrightnessMode @@ -833,7 +847,7 @@ public abstract class BrightnessMappingStrategy { public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits, float[] brightness, float maxGamma, @AutomaticBrightnessController.AutomaticBrightnessMode int mode, - DisplayWhiteBalanceController displayWhiteBalanceController) { + @Nullable DisplayWhiteBalanceController displayWhiteBalanceController) { Preconditions.checkArgument(nits.length != 0 && brightness.length != 0, "Nits and brightness arrays must not be empty!"); diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 7d22a87065b4..bd22e1d21dea 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -1591,25 +1591,18 @@ public class DisplayDeviceConfig { } /** - * @return The default auto-brightness brightening ambient lux levels - */ - public float[] getAutoBrightnessBrighteningLevelsLux() { - if (mDisplayBrightnessMapping == null) { - return null; - } - return mDisplayBrightnessMapping.getLuxArray(); - } - - /** * @param mode The auto-brightness mode - * @param setting The brightness setting - * @return Auto brightness brightening ambient lux levels for the specified mode and setting + * @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 The default auto-brightness brightening ambient lux levels for the specified mode + * and preset */ - public float[] getAutoBrightnessBrighteningLevelsLux(String mode, String setting) { + public float[] getAutoBrightnessBrighteningLevelsLux( + @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) { if (mDisplayBrightnessMapping == null) { return null; } - return mDisplayBrightnessMapping.getLuxArray(mode, setting); + return mDisplayBrightnessMapping.getLuxArray(mode, preset); } /** @@ -1623,25 +1616,17 @@ public class DisplayDeviceConfig { } /** - * @return The default auto-brightness brightening levels - */ - public float[] getAutoBrightnessBrighteningLevels() { - if (mDisplayBrightnessMapping == null) { - return null; - } - return mDisplayBrightnessMapping.getBrightnessArray(); - } - - /** * @param mode The auto-brightness mode - * @param setting The brightness setting - * @return Auto brightness brightening backlight levels for the specified mode and setting + * @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 The default auto-brightness brightening levels for the specified mode and preset */ - public float[] getAutoBrightnessBrighteningLevels(String mode, String setting) { + public float[] getAutoBrightnessBrighteningLevels( + @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) { if (mDisplayBrightnessMapping == null) { return null; } - return mDisplayBrightnessMapping.getBrightnessArray(mode, setting); + return mDisplayBrightnessMapping.getBrightnessArray(mode, preset); } /** diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java index 4e341a9c19b4..c17709144e1d 100644 --- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java +++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java @@ -65,6 +65,16 @@ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.Display return true; } + @Override + public float[] getCurrentAutoBrightnessLevels() { + return mDisplayPowerController.getCurrentAutoBrightnessLevels(); + } + + @Override + public float[] getCurrentAutoBrightnessLuxLevels() { + return mDisplayPowerController.getCurrentAutoBrightnessLuxLevels(); + } + /** * Start the offload session. The method returns if the session is already active. * @return Whether the session was started successfully diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 2685efecd6a1..c17378d790bd 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -735,7 +735,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mCdsi = null; } - setUpAutoBrightness(resources, handler); + setUpAutoBrightness(context, handler); mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic() && !resources.getBoolean( @@ -1075,7 +1075,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call loadBrightnessRampRates(); loadProximitySensor(); loadNitsRange(mContext.getResources()); - setUpAutoBrightness(mContext.getResources(), mHandler); + setUpAutoBrightness(mContext, mHandler); reloadReduceBrightColours(); setAnimatorRampSpeeds(/* isIdleMode= */ false); mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig); @@ -1119,7 +1119,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT, DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT); setAnimatorRampSpeeds(mAutomaticBrightnessController != null - && mAutomaticBrightnessController.isInIdleMode()); + && mAutomaticBrightnessController.getMode() == AUTO_BRIGHTNESS_MODE_IDLE); mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener); noteScreenState(mPowerState.getScreenState()); @@ -1145,7 +1145,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call handleBrightnessModeChange(); } - private void setUpAutoBrightness(Resources resources, Handler handler) { + private void setUpAutoBrightness(Context context, Handler handler) { mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable(); if (!mUseSoftwareAutoBrightnessConfig) { @@ -1155,21 +1155,19 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>(); BrightnessMappingStrategy defaultModeBrightnessMapper = - mInjector.getDefaultModeBrightnessMapper(resources, mDisplayDeviceConfig, + mInjector.getDefaultModeBrightnessMapper(context, mDisplayDeviceConfig, mDisplayWhiteBalanceController); brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT, defaultModeBrightnessMapper); - final boolean isIdleScreenBrightnessEnabled = resources.getBoolean( + final boolean isIdleScreenBrightnessEnabled = context.getResources().getBoolean( R.bool.config_enableIdleScreenBrightnessMode); if (isIdleScreenBrightnessEnabled) { BrightnessMappingStrategy idleModeBrightnessMapper = - BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig, - AUTO_BRIGHTNESS_MODE_IDLE, - mDisplayWhiteBalanceController); + BrightnessMappingStrategy.create(context, mDisplayDeviceConfig, + AUTO_BRIGHTNESS_MODE_IDLE, mDisplayWhiteBalanceController); if (idleModeBrightnessMapper != null) { - brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE, - idleModeBrightnessMapper); + brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE, idleModeBrightnessMapper); } } @@ -1181,7 +1179,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } if (defaultModeBrightnessMapper != null) { - final float dozeScaleFactor = resources.getFraction( + final float dozeScaleFactor = context.getResources().getFraction( com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor, 1, 1); @@ -1265,14 +1263,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call .getAutoBrightnessBrighteningLightDebounceIdle(); long darkeningLightDebounceIdle = mDisplayDeviceConfig .getAutoBrightnessDarkeningLightDebounceIdle(); - boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean( + boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean( com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); - int lightSensorWarmUpTimeConfig = resources.getInteger( + int lightSensorWarmUpTimeConfig = context.getResources().getInteger( com.android.internal.R.integer.config_lightSensorWarmupTime); - int lightSensorRate = resources.getInteger( + int lightSensorRate = context.getResources().getInteger( com.android.internal.R.integer.config_autoBrightnessLightSensorRate); - int initialLightSensorRate = resources.getInteger( + int initialLightSensorRate = context.getResources().getInteger( com.android.internal.R.integer.config_autoBrightnessInitialLightSensorRate); if (initialLightSensorRate == -1) { initialLightSensorRate = lightSensorRate; @@ -1992,7 +1990,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call boolean isIncreasing = animateValue > currentBrightness; final float rampSpeed; final boolean idle = mAutomaticBrightnessController != null - && mAutomaticBrightnessController.isInIdleMode(); + && mAutomaticBrightnessController.getMode() + == AUTO_BRIGHTNESS_MODE_IDLE; if (isIncreasing && slowChange) { rampSpeed = idle ? mBrightnessRampRateSlowIncreaseIdle : mBrightnessRampRateSlowIncrease; @@ -2216,6 +2215,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } @Override + public float[] getCurrentAutoBrightnessLevels() { + // The old DPC is no longer supported + return new float[0]; + } + + @Override + public float[] getCurrentAutoBrightnessLuxLevels() { + // The old DPC is no longer supported + return new float[0]; + } + + @Override public BrightnessInfo getBrightnessInfo() { synchronized (mCachedBrightnessInfo) { return new BrightnessInfo( @@ -2900,7 +2911,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // or the nits is invalid. if (brightnessIsTemporary || mAutomaticBrightnessController == null - || mAutomaticBrightnessController.isInIdleMode() + || mAutomaticBrightnessController.getMode() == AUTO_BRIGHTNESS_MODE_IDLE || !autobrightnessEnabled || mBrightnessTracker == null || !mUseAutoBrightness @@ -3645,12 +3656,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call userNits); } - BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources, + BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context, DisplayDeviceConfig displayDeviceConfig, DisplayWhiteBalanceController displayWhiteBalanceController) { - return BrightnessMappingStrategy.create(resources, - displayDeviceConfig, AUTO_BRIGHTNESS_MODE_DEFAULT, - displayWhiteBalanceController); + return BrightnessMappingStrategy.create(context, displayDeviceConfig, + AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController); } HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 6d09cc9d37ba..2efda087d4ac 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -17,7 +17,9 @@ 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 static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessPresetToString; import android.animation.Animator; import android.animation.ObjectAnimator; @@ -623,7 +625,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mCdsi = null; } - setUpAutoBrightness(resources, handler); + setUpAutoBrightness(context, handler); mColorFadeEnabled = mInjector.isColorFadeEnabled() && !resources.getBoolean( @@ -904,7 +906,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // updated here. loadBrightnessRampRates(); loadNitsRange(mContext.getResources()); - setUpAutoBrightness(mContext.getResources(), mHandler); + setUpAutoBrightness(mContext, mHandler); reloadReduceBrightColours(); setAnimatorRampSpeeds(/* isIdleMode= */ false); @@ -949,7 +951,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT, DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT); setAnimatorRampSpeeds(mAutomaticBrightnessController != null - && mAutomaticBrightnessController.isInIdleMode()); + && mAutomaticBrightnessController.getMode() == AUTO_BRIGHTNESS_MODE_IDLE); mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener); noteScreenState(mPowerState.getScreenState()); @@ -975,10 +977,15 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mContext.getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE), false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL); + if (mFlags.areAutoBrightnessModesEnabled()) { + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_ALS), + /* notifyForDescendants= */ false, mSettingsObserver, UserHandle.USER_CURRENT); + } handleBrightnessModeChange(); } - private void setUpAutoBrightness(Resources resources, Handler handler) { + private void setUpAutoBrightness(Context context, Handler handler) { mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable(); if (!mUseSoftwareAutoBrightnessConfig) { @@ -988,16 +995,16 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>(); BrightnessMappingStrategy defaultModeBrightnessMapper = - mInjector.getDefaultModeBrightnessMapper(resources, mDisplayDeviceConfig, + mInjector.getDefaultModeBrightnessMapper(context, mDisplayDeviceConfig, mDisplayWhiteBalanceController); brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT, defaultModeBrightnessMapper); - final boolean isIdleScreenBrightnessEnabled = resources.getBoolean( + final boolean isIdleScreenBrightnessEnabled = context.getResources().getBoolean( R.bool.config_enableIdleScreenBrightnessMode); if (isIdleScreenBrightnessEnabled) { BrightnessMappingStrategy idleModeBrightnessMapper = - BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig, + BrightnessMappingStrategy.create(context, mDisplayDeviceConfig, AUTO_BRIGHTNESS_MODE_IDLE, mDisplayWhiteBalanceController); if (idleModeBrightnessMapper != null) { @@ -1006,6 +1013,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } } + BrightnessMappingStrategy dozeModeBrightnessMapper = + BrightnessMappingStrategy.create(context, 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) { @@ -1014,7 +1028,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } if (defaultModeBrightnessMapper != null) { - final float dozeScaleFactor = resources.getFraction( + final float dozeScaleFactor = context.getResources().getFraction( R.fraction.config_screenAutoBrightnessDozeScaleFactor, 1, 1); @@ -1098,14 +1112,14 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal .getAutoBrightnessBrighteningLightDebounceIdle(); long darkeningLightDebounceIdle = mDisplayDeviceConfig .getAutoBrightnessDarkeningLightDebounceIdle(); - boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean( + boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean( R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); - int lightSensorWarmUpTimeConfig = resources.getInteger( + int lightSensorWarmUpTimeConfig = context.getResources().getInteger( R.integer.config_lightSensorWarmupTime); - int lightSensorRate = resources.getInteger( + int lightSensorRate = context.getResources().getInteger( R.integer.config_autoBrightnessLightSensorRate); - int initialLightSensorRate = resources.getInteger( + int initialLightSensorRate = context.getResources().getInteger( R.integer.config_autoBrightnessInitialLightSensorRate); if (initialLightSensorRate == -1) { initialLightSensorRate = lightSensorRate; @@ -1349,6 +1363,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.getMode() != AUTO_BRIGHTNESS_MODE_IDLE) { + setAutomaticScreenBrightnessMode(Display.isDozeState(state) + ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT); + } + final boolean userSetBrightnessChanged = mDisplayBrightnessController .updateUserSetScreenBrightness(); @@ -1633,7 +1654,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal boolean isIncreasing = animateValue > currentBrightness; final float rampSpeed; final boolean idle = mAutomaticBrightnessController != null - && mAutomaticBrightnessController.isInIdleMode(); + && mAutomaticBrightnessController.getMode() + == AUTO_BRIGHTNESS_MODE_IDLE; if (isIncreasing && slowChange) { rampSpeed = idle ? mBrightnessRampRateSlowIncreaseIdle : mBrightnessRampRateSlowIncrease; @@ -1865,6 +1887,18 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } @Override + public float[] getCurrentAutoBrightnessLevels() { + return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels( + mAutomaticBrightnessController.getMode()); + } + + @Override + public float[] getCurrentAutoBrightnessLuxLevels() { + return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux( + mAutomaticBrightnessController.getMode()); + } + + @Override public BrightnessInfo getBrightnessInfo() { synchronized (mCachedBrightnessInfo) { return new BrightnessInfo( @@ -2439,7 +2473,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // or the nits is invalid. if (brightnessIsTemporary || mAutomaticBrightnessController == null - || mAutomaticBrightnessController.isInIdleMode() + || mAutomaticBrightnessController.getMode() == AUTO_BRIGHTNESS_MODE_IDLE || !autobrightnessEnabled || mBrightnessTracker == null || !shouldUseAutoBrightness @@ -2984,6 +3018,16 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal public void onChange(boolean selfChange, Uri uri) { if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) { handleBrightnessModeChange(); + } else if (uri.equals(Settings.System.getUriFor( + Settings.System.SCREEN_BRIGHTNESS_FOR_ALS))) { + int preset = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_FOR_ALS, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, + UserHandle.USER_CURRENT); + Slog.i(mTag, "Setting up auto-brightness for preset " + + autoBrightnessPresetToString(preset)); + setUpAutoBrightness(mContext, mHandler); + sendUpdatePowerState(); } else { handleSettingsChange(false /* userSwitch */); } @@ -3102,12 +3146,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal userNits); } - BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources, + BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context, DisplayDeviceConfig displayDeviceConfig, DisplayWhiteBalanceController displayWhiteBalanceController) { - return BrightnessMappingStrategy.create(resources, - displayDeviceConfig, AUTO_BRIGHTNESS_MODE_DEFAULT, - displayWhiteBalanceController); + return BrightnessMappingStrategy.create(context, displayDeviceConfig, + AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController); } HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java index c27918430254..5b350db5fa7b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java +++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java @@ -237,4 +237,14 @@ public interface DisplayPowerControllerInterface { * Indicate that boot has been completed and the screen is ready to update. */ void onBootCompleted(); + + /** + * Get the current brightness levels used to determine automatic brightness based on lux levels. + */ + float[] getCurrentAutoBrightnessLevels(); + + /** + * Get the current lux levels used to determine automatic brightness. + */ + float[] getCurrentAutoBrightnessLuxLevels(); } diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java index 21628503adcf..6978686faa12 100644 --- a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java +++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java @@ -16,11 +16,17 @@ 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.provider.Settings; 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; @@ -33,7 +39,9 @@ import java.util.Map; */ public class DisplayBrightnessMappingConfig { - private static final String DEFAULT_BRIGHTNESS_MAPPING_KEY = "default_normal"; + private static final String DEFAULT_BRIGHTNESS_MAPPING_KEY = + AutoBrightnessModeName._default.getRawName() + "_" + + AutoBrightnessSettingName.normal.getRawName(); /** * Array of desired screen brightness in nits corresponding to the lux values @@ -45,19 +53,22 @@ public class DisplayBrightnessMappingConfig { /** * Map of arrays of desired screen brightness corresponding to the lux values - * in mBrightnessLevelsLuxMap, indexed by the auto-brightness mode and the brightness setting. + * 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 setting - * separated by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal, - * doze_dim, doze_bright. + * 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 setting. + * indexed by the auto-brightness mode and the brightness preset. * * The first lux value in every array is always 0. * @@ -69,9 +80,12 @@ public class DisplayBrightnessMappingConfig { * 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 setting - * separated by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal, - * doze_dim, doze_bright. + * 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<>(); @@ -138,19 +152,16 @@ public class DisplayBrightnessMappingConfig { } /** - * @return The default auto-brightness brightening ambient lux levels - */ - public float[] getLuxArray() { - return mBrightnessLevelsLuxMap.get(DEFAULT_BRIGHTNESS_MAPPING_KEY); - } - - /** * @param mode The auto-brightness mode - * @param setting The brightness setting - * @return Auto brightness brightening ambient lux levels for the specified mode and setting + * @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 The default auto-brightness brightening ambient lux levels for the specified mode + * and preset */ - public float[] getLuxArray(String mode, String setting) { - return mBrightnessLevelsLuxMap.get(mode + "_" + setting); + public float[] getLuxArray(@AutomaticBrightnessController.AutomaticBrightnessMode int mode, + int preset) { + return mBrightnessLevelsLuxMap.get( + autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset)); } /** @@ -161,19 +172,15 @@ public class DisplayBrightnessMappingConfig { } /** - * @return The default auto-brightness brightening levels - */ - public float[] getBrightnessArray() { - return mBrightnessLevelsMap.get(DEFAULT_BRIGHTNESS_MAPPING_KEY); - } - - /** * @param mode The auto-brightness mode - * @param setting The brightness setting - * @return Auto brightness brightening ambient lux levels for the specified mode and setting + * @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 The default auto-brightness brightening levels for the specified mode and preset */ - public float[] getBrightnessArray(String mode, String setting) { - return mBrightnessLevelsMap.get(mode + "_" + setting); + public float[] getBrightnessArray( + @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) { + return mBrightnessLevelsMap.get( + autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset)); } @Override @@ -205,6 +212,44 @@ public class DisplayBrightnessMappingConfig { + ", 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); + } + } + + /** + * @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 The string representing the preset + */ + public static String autoBrightnessPresetToString(int preset) { + return switch (preset) { + case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM -> + AutoBrightnessSettingName.dim.getRawName(); + case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL -> + AutoBrightnessSettingName.normal.getRawName(); + case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT -> + AutoBrightnessSettingName.bright.getRawName(); + default -> throw new IllegalArgumentException( + "Unknown auto-brightness preset value: " + preset); + }; + } + private float[] brightnessArrayIntToFloat(int[] brightnessInt, Spline backlightToBrightnessSpline) { float[] brightnessFloat = new float[brightnessInt.length]; 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/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index 773293f83323..a6c5ad5ffc6c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -356,15 +356,6 @@ final class InputMethodUtils { new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR)); } - List<String> getEnabledInputMethodNames() { - List<String> result = new ArrayList<>(); - for (Pair<String, ArrayList<String>> pair : - getEnabledInputMethodsAndSubtypeListLocked()) { - result.add(pair.first); - } - return result; - } - void appendAndPutEnabledInputMethodLocked(String id) { if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) { // Add in the newly enabled input method. 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/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java index 850449595d74..0eb9166371dc 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java @@ -87,6 +87,8 @@ import java.util.Objects; @NonNull private final AudioDeviceCallback mAudioDeviceCallback = new AudioDeviceCallbackImpl(); + @MediaRoute2Info.SuitabilityStatus private final int mBuiltInSpeakerSuitabilityStatus; + @NonNull private final AudioManager.OnDevicesForAttributesChangedListener mOnDevicesForAttributesChangedListener = this::onDevicesForAttributesChangedListener; @@ -113,6 +115,10 @@ import java.util.Objects; mHandler = new Handler(Objects.requireNonNull(looper)); mStrategyForMedia = Objects.requireNonNull(strategyForMedia); mOnDeviceRouteChangedListener = Objects.requireNonNull(onDeviceRouteChangedListener); + + mBuiltInSpeakerSuitabilityStatus = + DeviceRouteController.getBuiltInSpeakerSuitabilityStatus(mContext); + mBluetoothRouteController = new AudioPoliciesBluetoothRouteController( mContext, btAdapter, this::rebuildAvailableRoutesAndNotify); @@ -373,14 +379,19 @@ import java.util.Objects; // from getting an id using BluetoothRouteController#getRouteIdForBluetoothAddress. routeId = systemRouteInfo.mDefaultRouteId; } - return new MediaRoute2Info.Builder(routeId, humanReadableName) + MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(routeId, humanReadableName) .setType(systemRouteInfo.mMediaRoute2InfoType) .setAddress(address) .setSystemRoute(true) .addFeature(FEATURE_LIVE_AUDIO) .addFeature(FEATURE_LOCAL_PLAYBACK) - .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED) - .build(); + .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED); + + if (systemRouteInfo.mMediaRoute2InfoType == MediaRoute2Info.TYPE_BUILTIN_SPEAKER) { + builder.setSuitabilityStatus(mBuiltInSpeakerSuitabilityStatus); + } + + return builder.build(); } /** diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java index 9f175a9a0277..8b62cc974862 100644 --- a/services/core/java/com/android/server/media/DeviceRouteController.java +++ b/services/core/java/com/android/server/media/DeviceRouteController.java @@ -81,6 +81,30 @@ import java.util.List; } } + /** Returns device route availability status. */ + @MediaRoute2Info.SuitabilityStatus + static int getBuiltInSpeakerSuitabilityStatus(@NonNull Context context) { + if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { + // Route is always suitable if the flag is disabled. + return MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER; + } + + int availabilityStatus = + context.getResources() + .getInteger( + com.android.internal.R.integer + .config_mediaRouter_builtInSpeakerSuitability); + + switch (availabilityStatus) { + case MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER: + case MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER: + case MediaRoute2Info.SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER: + return availabilityStatus; + default: + return MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER; + } + } + /** Returns the currently selected device (built-in or wired) route. */ @NonNull MediaRoute2Info getSelectedRoute(); diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java index c0f28346705c..65b0ad0d61a0 100644 --- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java +++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java @@ -72,6 +72,8 @@ import java.util.Objects; @NonNull private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver(); + @MediaRoute2Info.SuitabilityStatus private final int mBuiltInSpeakerSuitabilityStatus; + private int mDeviceVolume; private MediaRoute2Info mDeviceRoute; @@ -90,6 +92,9 @@ import java.util.Objects; mAudioManager = audioManager; mAudioService = audioService; + mBuiltInSpeakerSuitabilityStatus = + DeviceRouteController.getBuiltInSpeakerSuitabilityStatus(mContext); + AudioRoutesInfo newAudioRoutes = null; try { newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver); @@ -165,19 +170,28 @@ import java.util.Objects; } synchronized (this) { - return new MediaRoute2Info.Builder( - DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString()) - .setVolumeHandling(mAudioManager.isVolumeFixed() - ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED - : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) - .setVolume(mDeviceVolume) - .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) - .setType(type) - .addFeature(FEATURE_LIVE_AUDIO) - .addFeature(FEATURE_LIVE_VIDEO) - .addFeature(FEATURE_LOCAL_PLAYBACK) - .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED) - .build(); + MediaRoute2Info.Builder builder = + new MediaRoute2Info.Builder( + DEVICE_ROUTE_ID, + mContext.getResources().getText(name).toString()) + .setVolumeHandling( + mAudioManager.isVolumeFixed() + ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED + : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) + .setVolume(mDeviceVolume) + .setVolumeMax( + mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) + .setType(type) + .addFeature(FEATURE_LIVE_AUDIO) + .addFeature(FEATURE_LIVE_VIDEO) + .addFeature(FEATURE_LOCAL_PLAYBACK) + .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED); + + if (type == TYPE_BUILTIN_SPEAKER) { + builder.setSuitabilityStatus(mBuiltInSpeakerSuitabilityStatus); + } + + return builder.build(); } } diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 8149847d70c0..1bc2a5eb1351 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -24,6 +24,7 @@ import android.media.MediaRoute2ProviderInfo; import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; import android.os.Bundle; +import android.os.UserHandle; import com.android.internal.annotations.GuardedBy; @@ -54,8 +55,15 @@ abstract class MediaRoute2Provider { mCallback = callback; } - public abstract void requestCreateSession(long requestId, String packageName, String routeId, - @Nullable Bundle sessionHints); + public abstract void requestCreateSession( + long requestId, + String packageName, + String routeId, + @Nullable Bundle sessionHints, + @RoutingSessionInfo.TransferReason int transferReason, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName); + public abstract void releaseSession(long requestId, String sessionId); public abstract void updateDiscoveryPreference( @@ -63,7 +71,14 @@ abstract class MediaRoute2Provider { public abstract void selectRoute(long requestId, String sessionId, String routeId); public abstract void deselectRoute(long requestId, String sessionId, String routeId); - public abstract void transferToRoute(long requestId, String sessionId, String routeId); + + public abstract void transferToRoute( + long requestId, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName, + String sessionId, + String routeId, + @RoutingSessionInfo.TransferReason int transferReason); public abstract void setRouteVolume(long requestId, String routeId, int volume); public abstract void setSessionVolume(long requestId, String sessionId, int volume); diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index 330818ed17ca..ae889d8255c6 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -98,8 +98,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider } @Override - public void requestCreateSession(long requestId, String packageName, String routeId, - Bundle sessionHints) { + public void requestCreateSession( + long requestId, + String packageName, + String routeId, + Bundle sessionHints, + @RoutingSessionInfo.TransferReason int transferReason, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { if (mConnectionReady) { mActiveConnection.requestCreateSession(requestId, packageName, routeId, sessionHints); updateBinding(); @@ -141,7 +147,13 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider } @Override - public void transferToRoute(long requestId, String sessionId, String routeId) { + public void transferToRoute( + long requestId, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName, + String sessionId, + String routeId, + @RoutingSessionInfo.TransferReason int transferReason) { if (mConnectionReady) { mActiveConnection.transferToRoute(requestId, sessionId, routeId); } @@ -649,6 +661,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider + "Disallowed route: " + route); } + + if (route.getSuitabilityStatus() + == MediaRoute2Info.SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER) { + throw new SecurityException( + "Only the system is allowed to set not suitable for transfer status. " + + "Disallowed route: " + + route); + } } Connection connection = mConnectionRef.get(); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 5e18727459c6..38f0df41db04 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -337,18 +337,47 @@ class MediaRouter2ServiceImpl { } } - public void requestCreateSessionWithRouter2(@NonNull IMediaRouter2 router, int requestId, - long managerRequestId, @NonNull RoutingSessionInfo oldSession, - @NonNull MediaRoute2Info route, Bundle sessionHints) { + public void requestCreateSessionWithRouter2( + @NonNull IMediaRouter2 router, + int requestId, + long managerRequestId, + @NonNull RoutingSessionInfo oldSession, + @NonNull MediaRoute2Info route, + Bundle sessionHints, + @Nullable UserHandle transferInitiatorUserHandle, + @Nullable String transferInitiatorPackageName) { Objects.requireNonNull(router, "router must not be null"); Objects.requireNonNull(oldSession, "oldSession must not be null"); Objects.requireNonNull(route, "route must not be null"); + synchronized (mLock) { + if (managerRequestId == MediaRoute2ProviderService.REQUEST_ID_NONE + || transferInitiatorUserHandle == null + || transferInitiatorPackageName == null) { + final IBinder binder = router.asBinder(); + final RouterRecord routerRecord = mAllRouterRecords.get(binder); + + transferInitiatorUserHandle = Binder.getCallingUserHandle(); + if (routerRecord != null) { + transferInitiatorPackageName = routerRecord.mPackageName; + } else { + transferInitiatorPackageName = mContext.getPackageName(); + } + } + } + final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - requestCreateSessionWithRouter2Locked(requestId, managerRequestId, - router, oldSession, route, sessionHints); + requestCreateSessionWithRouter2Locked( + requestId, + managerRequestId, + transferInitiatorUserHandle, + transferInitiatorPackageName, + router, + oldSession, + route, + sessionHints); } } finally { Binder.restoreCallingIdentity(token); @@ -399,10 +428,11 @@ class MediaRouter2ServiceImpl { throw new IllegalArgumentException("uniqueSessionId must not be empty"); } + UserHandle userHandle = Binder.getCallingUserHandle(); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - transferToRouteWithRouter2Locked(router, uniqueSessionId, route); + transferToRouteWithRouter2Locked(router, userHandle, uniqueSessionId, route); } } finally { Binder.restoreCallingIdentity(token); @@ -588,16 +618,28 @@ class MediaRouter2ServiceImpl { } } - public void requestCreateSessionWithManager(@NonNull IMediaRouter2Manager manager, - int requestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) { + public void requestCreateSessionWithManager( + @NonNull IMediaRouter2Manager manager, + int requestId, + @NonNull RoutingSessionInfo oldSession, + @NonNull MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { Objects.requireNonNull(manager, "manager must not be null"); Objects.requireNonNull(oldSession, "oldSession must not be null"); Objects.requireNonNull(route, "route must not be null"); + Objects.requireNonNull(transferInitiatorUserHandle); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route); + requestCreateSessionWithManagerLocked( + requestId, + manager, + oldSession, + route, + transferInitiatorUserHandle, + transferInitiatorPackageName); } } finally { Binder.restoreCallingIdentity(token); @@ -640,18 +682,32 @@ class MediaRouter2ServiceImpl { } } - public void transferToRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId, - @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) { + public void transferToRouteWithManager( + @NonNull IMediaRouter2Manager manager, + int requestId, + @NonNull String uniqueSessionId, + @NonNull MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { Objects.requireNonNull(manager, "manager must not be null"); if (TextUtils.isEmpty(uniqueSessionId)) { throw new IllegalArgumentException("uniqueSessionId must not be empty"); } Objects.requireNonNull(route, "route must not be null"); + Objects.requireNonNull(transferInitiatorUserHandle); + Objects.requireNonNull(transferInitiatorPackageName); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - transferToRouteWithManagerLocked(requestId, manager, uniqueSessionId, route); + transferToRouteWithManagerLocked( + requestId, + manager, + uniqueSessionId, + route, + RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST, + transferInitiatorUserHandle, + transferInitiatorPackageName); } } finally { Binder.restoreCallingIdentity(token); @@ -1038,9 +1094,15 @@ class MediaRouter2ServiceImpl { } @GuardedBy("mLock") - private void requestCreateSessionWithRouter2Locked(int requestId, long managerRequestId, - @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession, - @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) { + private void requestCreateSessionWithRouter2Locked( + int requestId, + long managerRequestId, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName, + @NonNull IMediaRouter2 router, + @NonNull RoutingSessionInfo oldSession, + @NonNull MediaRoute2Info route, + @Nullable Bundle sessionHints) { final IBinder binder = router.asBinder(); final RouterRecord routerRecord = mAllRouterRecords.get(binder); @@ -1114,9 +1176,16 @@ class MediaRouter2ServiceImpl { long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId); routerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler, + obtainMessage( + UserHandler::requestCreateSessionWithRouter2OnHandler, routerRecord.mUserRecord.mHandler, - uniqueRequestId, managerRequestId, routerRecord, oldSession, route, + uniqueRequestId, + managerRequestId, + transferInitiatorUserHandle, + transferInitiatorPackageName, + routerRecord, + oldSession, + route, sessionHints)); } @@ -1165,8 +1234,11 @@ class MediaRouter2ServiceImpl { } @GuardedBy("mLock") - private void transferToRouteWithRouter2Locked(@NonNull IMediaRouter2 router, - @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) { + private void transferToRouteWithRouter2Locked( + @NonNull IMediaRouter2 router, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String uniqueSessionId, + @NonNull MediaRoute2Info route) { final IBinder binder = router.asBinder(); final RouterRecord routerRecord = mAllRouterRecords.get(binder); @@ -1191,9 +1263,16 @@ class MediaRouter2ServiceImpl { routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID))); } else { routerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::transferToRouteOnHandler, + obtainMessage( + UserHandler::transferToRouteOnHandler, routerRecord.mUserRecord.mHandler, - DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route)); + DUMMY_REQUEST_ID, + transferInitiatorUserHandle, + routerRecord.mPackageName, + routerRecord, + uniqueSessionId, + route, + RoutingSessionInfo.TRANSFER_REASON_APP)); } } @@ -1416,9 +1495,13 @@ class MediaRouter2ServiceImpl { } @GuardedBy("mLock") - private void requestCreateSessionWithManagerLocked(int requestId, - @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession, - @NonNull MediaRoute2Info route) { + private void requestCreateSessionWithManagerLocked( + int requestId, + @NonNull IMediaRouter2Manager manager, + @NonNull RoutingSessionInfo oldSession, + @NonNull MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder()); if (managerRecord == null) { return; @@ -1464,9 +1547,16 @@ class MediaRouter2ServiceImpl { // Before requesting to the provider, get session hints from the media router. // As a return, media router will request to create a session. routerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::requestRouterCreateSessionOnHandler, + obtainMessage( + UserHandler::requestRouterCreateSessionOnHandler, routerRecord.mUserRecord.mHandler, - uniqueRequestId, routerRecord, managerRecord, oldSession, route)); + uniqueRequestId, + routerRecord, + managerRecord, + oldSession, + route, + transferInitiatorUserHandle, + transferInitiatorPackageName)); } @GuardedBy("mLock") @@ -1521,9 +1611,14 @@ class MediaRouter2ServiceImpl { } @GuardedBy("mLock") - private void transferToRouteWithManagerLocked(int requestId, + private void transferToRouteWithManagerLocked( + int requestId, @NonNull IMediaRouter2Manager manager, - @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) { + @NonNull String uniqueSessionId, + @NonNull MediaRoute2Info route, + @RoutingSessionInfo.TransferReason int transferReason, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { final IBinder binder = manager.asBinder(); ManagerRecord managerRecord = mAllManagerRecords.get(binder); @@ -1541,9 +1636,16 @@ class MediaRouter2ServiceImpl { long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId); managerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::transferToRouteOnHandler, + obtainMessage( + UserHandler::transferToRouteOnHandler, managerRecord.mUserRecord.mHandler, - uniqueRequestId, routerRecord, uniqueSessionId, route)); + uniqueRequestId, + transferInitiatorUserHandle, + transferInitiatorPackageName, + routerRecord, + uniqueSessionId, + route, + transferReason)); } @GuardedBy("mLock") @@ -1850,6 +1952,19 @@ class MediaRouter2ServiceImpl { } } + public void notifySessionCreated(int requestId, @NonNull RoutingSessionInfo sessionInfo) { + try { + mRouter.notifySessionCreated( + requestId, maybeClearTransferInitiatorIdentity(sessionInfo)); + } catch (RemoteException ex) { + Slog.w( + TAG, + "Failed to notify router of the session creation." + + " Router probably died.", + ex); + } + } + /** * Sends the corresponding router an update for the given session. * @@ -1857,12 +1972,27 @@ class MediaRouter2ServiceImpl { */ public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) { try { - mRouter.notifySessionInfoChanged(sessionInfo); + mRouter.notifySessionInfoChanged(maybeClearTransferInitiatorIdentity(sessionInfo)); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex); } } + private RoutingSessionInfo maybeClearTransferInitiatorIdentity( + @NonNull RoutingSessionInfo sessionInfo) { + UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle(); + String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName(); + + if (!Objects.equals(UserHandle.of(mUserRecord.mUserId), transferInitiatorUserHandle) + || !Objects.equals(mPackageName, transferInitiatorPackageName)) { + return new RoutingSessionInfo.Builder(sessionInfo) + .setTransferInitiator(null, null) + .build(); + } + + return sessionInfo; + } + /** * Returns a filtered copy of {@code routes} that contains only the routes that are {@link * MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record. @@ -2307,9 +2437,14 @@ class MediaRouter2ServiceImpl { return -1; } - private void requestRouterCreateSessionOnHandler(long uniqueRequestId, - @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord, - @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) { + private void requestRouterCreateSessionOnHandler( + long uniqueRequestId, + @NonNull RouterRecord routerRecord, + @NonNull ManagerRecord managerRecord, + @NonNull RoutingSessionInfo oldSession, + @NonNull MediaRoute2Info route, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { try { if (route.isSystemRoute() && !routerRecord.hasSystemRoutingPermission()) { // The router lacks permission to modify system routing, so we hide system @@ -2317,7 +2452,11 @@ class MediaRouter2ServiceImpl { route = mSystemProvider.getDefaultRoute(); } routerRecord.mRouter.requestCreateSessionByManager( - uniqueRequestId, oldSession, route); + uniqueRequestId, + oldSession, + route, + transferInitiatorUserHandle, + transferInitiatorPackageName); } catch (RemoteException ex) { Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: " + "Failed to request. Router probably died.", ex); @@ -2326,10 +2465,15 @@ class MediaRouter2ServiceImpl { } } - private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId, - long managerRequestId, @NonNull RouterRecord routerRecord, + private void requestCreateSessionWithRouter2OnHandler( + long uniqueRequestId, + long managerRequestId, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName, + @NonNull RouterRecord routerRecord, @NonNull RoutingSessionInfo oldSession, - @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) { + @NonNull MediaRoute2Info route, + @Nullable Bundle sessionHints) { final MediaRoute2Provider provider = findProvider(route.getProviderId()); if (provider == null) { @@ -2345,8 +2489,19 @@ class MediaRouter2ServiceImpl { managerRequestId, oldSession, route); mSessionCreationRequests.add(request); - provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName, - route.getOriginalId(), sessionHints); + int transferReason = RoutingSessionInfo.TRANSFER_REASON_APP; + if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) { + transferReason = RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST; + } + + provider.requestCreateSession( + uniqueRequestId, + routerRecord.mPackageName, + route.getOriginalId(), + sessionHints, + transferReason, + transferInitiatorUserHandle, + transferInitiatorPackageName); } // routerRecord can be null if the session is system's or RCN. @@ -2386,9 +2541,14 @@ class MediaRouter2ServiceImpl { } // routerRecord can be null if the session is system's or RCN. - private void transferToRouteOnHandler(long uniqueRequestId, + private void transferToRouteOnHandler( + long uniqueRequestId, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName, @Nullable RouterRecord routerRecord, - @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) { + @NonNull String uniqueSessionId, + @NonNull MediaRoute2Info route, + @RoutingSessionInfo.TransferReason int transferReason) { if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route, "transferring to")) { return; @@ -2399,8 +2559,13 @@ class MediaRouter2ServiceImpl { if (provider == null) { return; } - provider.transferToRoute(uniqueRequestId, getOriginalId(uniqueSessionId), - route.getOriginalId()); + provider.transferToRoute( + uniqueRequestId, + transferInitiatorUserHandle, + transferInitiatorPackageName, + getOriginalId(uniqueSessionId), + route.getOriginalId(), + transferReason); } // routerRecord is null if and only if the session is created without the request, which @@ -2535,6 +2700,7 @@ class MediaRouter2ServiceImpl { // session info from them. sessionInfo = mSystemProvider.getDefaultSessionInfo(); } + // TODO: b/279555229 - replace with matchingRequest.mRouterRecord.notifySessionCreated. notifySessionCreatedToRouter( matchingRequest.mRouterRecord, toOriginalRequestId(uniqueRequestId), @@ -2648,12 +2814,7 @@ class MediaRouter2ServiceImpl { private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord, int requestId, @NonNull RoutingSessionInfo sessionInfo) { - try { - routerRecord.mRouter.notifySessionCreated(requestId, sessionInfo); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify router of the session creation." - + " Router probably died.", ex); - } + routerRecord.notifySessionCreated(requestId, sessionInfo); } private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord, diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index e562b3f0845c..7dd13142f52e 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -461,11 +461,24 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, - long managerRequestId, RoutingSessionInfo oldSession, - MediaRoute2Info route, Bundle sessionHints) { - mService2.requestCreateSessionWithRouter2(router, requestId, managerRequestId, - oldSession, route, sessionHints); + public void requestCreateSessionWithRouter2( + IMediaRouter2 router, + int requestId, + long managerRequestId, + RoutingSessionInfo oldSession, + MediaRoute2Info route, + Bundle sessionHints, + @Nullable UserHandle transferInitiatorUserHandle, + @Nullable String transferInitiatorPackageName) { + mService2.requestCreateSessionWithRouter2( + router, + requestId, + managerRequestId, + oldSession, + route, + sessionHints, + transferInitiatorUserHandle, + transferInitiatorPackageName); } // Binder call @@ -580,9 +593,20 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void requestCreateSessionWithManager(IMediaRouter2Manager manager, - int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route) { - mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route); + public void requestCreateSessionWithManager( + IMediaRouter2Manager manager, + int requestId, + RoutingSessionInfo oldSession, + MediaRoute2Info route, + UserHandle transferInitiatorUserHandle, + String transferInitiatorPackageName) { + mService2.requestCreateSessionWithManager( + manager, + requestId, + oldSession, + route, + transferInitiatorUserHandle, + transferInitiatorPackageName); } // Binder call @@ -601,9 +625,20 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId, - String sessionId, MediaRoute2Info route) { - mService2.transferToRouteWithManager(manager, requestId, sessionId, route); + public void transferToRouteWithManager( + IMediaRouter2Manager manager, + int requestId, + String sessionId, + MediaRoute2Info route, + UserHandle transferInitiatorUserHandle, + String transferInitiatorPackageName) { + mService2.transferToRouteWithManager( + manager, + requestId, + sessionId, + route, + transferInitiatorUserHandle, + transferInitiatorPackageName); } // Binder call diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 9d151c27e7c7..f7210dd1ef70 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -16,6 +16,7 @@ package com.android.server.media; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -26,6 +27,7 @@ import android.media.AudioManager; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; +import android.media.MediaRouter2Utils; import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; import android.os.Bundle; @@ -39,6 +41,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.media.flags.Flags; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; @@ -79,6 +82,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { @GuardedBy("mRequestLock") private volatile SessionCreationRequest mPendingSessionCreationRequest; + private final Object mTransferLock = new Object(); + @GuardedBy("mTransferLock") + @Nullable private volatile SessionCreationRequest mPendingTransferRequest; + SystemMediaRoute2Provider(Context context, UserHandle user) { super(COMPONENT_NAME); mIsSystemRouteProvider = true; @@ -146,17 +153,30 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } @Override - public void requestCreateSession(long requestId, String packageName, String routeId, - Bundle sessionHints) { + public void requestCreateSession( + long requestId, + String packageName, + String routeId, + Bundle sessionHints, + @RoutingSessionInfo.TransferReason int transferReason, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { // Assume a router without MODIFY_AUDIO_ROUTING permission can't request with // a route ID different from the default route ID. The service should've filtered. if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) { mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo); return; } - if (TextUtils.equals(routeId, mSelectedRouteId)) { - mCallback.onSessionCreated(this, requestId, mSessionInfos.get(0)); - return; + + if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { + if (TextUtils.equals(routeId, mSelectedRouteId)) { + RoutingSessionInfo currentSessionInfo; + synchronized (mLock) { + currentSessionInfo = mSessionInfos.get(0); + } + mCallback.onSessionCreated(this, requestId, currentSessionInfo); + return; + } } synchronized (mRequestLock) { @@ -165,10 +185,23 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mCallback.onRequestFailed(this, mPendingSessionCreationRequest.mRequestId, MediaRoute2ProviderService.REASON_UNKNOWN_ERROR); } - mPendingSessionCreationRequest = new SessionCreationRequest(requestId, routeId); + mPendingSessionCreationRequest = + new SessionCreationRequest( + requestId, + routeId, + RoutingSessionInfo.TRANSFER_REASON_FALLBACK, + transferInitiatorUserHandle, + transferInitiatorPackageName); } - transferToRoute(requestId, SYSTEM_SESSION_ID, routeId); + // Only unprivileged routers call this method, therefore we use TRANSFER_REASON_APP. + transferToRoute( + requestId, + transferInitiatorUserHandle, + transferInitiatorPackageName, + SYSTEM_SESSION_ID, + routeId, + transferReason); } @Override @@ -193,12 +226,31 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } @Override - public void transferToRoute(long requestId, String sessionId, String routeId) { + public void transferToRoute( + long requestId, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName, + String sessionId, + String routeId, + @RoutingSessionInfo.TransferReason int transferReason) { if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) { // The currently selected route is the default route. Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT); return; } + + if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { + synchronized (mTransferLock) { + mPendingTransferRequest = + new SessionCreationRequest( + requestId, + routeId, + transferReason, + transferInitiatorUserHandle, + transferInitiatorPackageName); + } + } + MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute(); boolean isAvailableDeviceRoute = mDeviceRouteController.getAvailableRoutes().stream() @@ -218,6 +270,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mDeviceRouteController.transferTo(null); mBluetoothRouteController.transferTo(routeId); } + + if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses() + && updateSessionInfosIfNeeded()) { + notifySessionInfoUpdated(); + } } @Override @@ -322,9 +379,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute(); MediaRoute2Info selectedRoute = selectedDeviceRoute; MediaRoute2Info selectedBtRoute = mBluetoothRouteController.getSelectedRoute(); + List<String> transferableRoutes = new ArrayList<>(); + if (selectedBtRoute != null) { selectedRoute = selectedBtRoute; - builder.addTransferableRoute(selectedDeviceRoute.getId()); + transferableRoutes.add(selectedDeviceRoute.getId()); } mSelectedRouteId = selectedRoute.getId(); mDefaultRoute = @@ -337,12 +396,54 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) { String routeId = route.getId(); if (!mSelectedRouteId.equals(routeId)) { - builder.addTransferableRoute(routeId); + transferableRoutes.add(routeId); } } } for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) { - builder.addTransferableRoute(route.getId()); + transferableRoutes.add(route.getId()); + } + + for (String route : transferableRoutes) { + builder.addTransferableRoute(route); + } + + if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { + int transferReason = RoutingSessionInfo.TRANSFER_REASON_FALLBACK; + UserHandle transferInitiatorUserHandle = null; + String transferInitiatorPackageName = null; + + if (oldSessionInfo != null + && containsSelectedRouteWithId(oldSessionInfo, selectedRoute.getId())) { + transferReason = oldSessionInfo.getTransferReason(); + transferInitiatorUserHandle = oldSessionInfo.getTransferInitiatorUserHandle(); + transferInitiatorPackageName = oldSessionInfo.getTransferInitiatorPackageName(); + } + + synchronized (mTransferLock) { + if (mPendingTransferRequest != null) { + boolean isTransferringToTheSelectedRoute = + mPendingTransferRequest.isTargetRoute(selectedRoute); + boolean canBePotentiallyTransferred = + mPendingTransferRequest.isInsideOfRoutesList(transferableRoutes); + + if (isTransferringToTheSelectedRoute) { + transferReason = mPendingTransferRequest.mTransferReason; + transferInitiatorUserHandle = + mPendingTransferRequest.mTransferInitiatorUserHandle; + transferInitiatorPackageName = + mPendingTransferRequest.mTransferInitiatorPackageName; + + mPendingTransferRequest = null; + } else if (!canBePotentiallyTransferred) { + mPendingTransferRequest = null; + } + } + } + + builder.setTransferReason(transferReason) + .setTransferInitiator( + transferInitiatorUserHandle, transferInitiatorPackageName); } RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build(); @@ -424,6 +525,22 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { return false; } + private boolean containsSelectedRouteWithId( + @Nullable RoutingSessionInfo sessionInfo, @NonNull String selectedRouteId) { + if (sessionInfo == null) { + return false; + } + + List<String> selectedRoutes = sessionInfo.getSelectedRoutes(); + + if (selectedRoutes.size() != 1) { + throw new IllegalStateException("Selected routes list should contain only 1 route id."); + } + + String oldSelectedRouteId = MediaRouter2Utils.getOriginalId(selectedRoutes.get(0)); + return oldSelectedRouteId != null && oldSelectedRouteId.equals(selectedRouteId); + } + void publishProviderState() { updateProviderState(); notifyProviderState(); @@ -452,12 +569,47 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } private static class SessionCreationRequest { - final long mRequestId; - final String mRouteId; + private final long mRequestId; + @NonNull private final String mRouteId; + + @RoutingSessionInfo.TransferReason private final int mTransferReason; + + @NonNull private final UserHandle mTransferInitiatorUserHandle; + @NonNull private final String mTransferInitiatorPackageName; + + SessionCreationRequest( + long requestId, + @NonNull String routeId, + @RoutingSessionInfo.TransferReason int transferReason, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { + mRequestId = requestId; + mRouteId = routeId; + mTransferReason = transferReason; + mTransferInitiatorUserHandle = transferInitiatorUserHandle; + mTransferInitiatorPackageName = transferInitiatorPackageName; + } + + private boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) { + if (route2Info == null) { + return false; + } + + return isTargetRoute(route2Info.getId()); + } + + private boolean isTargetRoute(@Nullable String routeId) { + return mRouteId.equals(routeId); + } + + private boolean isInsideOfRoutesList(@NonNull List<String> routesList) { + for (String routeId : routesList) { + if (isTargetRoute(routeId)) { + return true; + } + } - SessionCreationRequest(long requestId, String routeId) { - this.mRequestId = requestId; - this.mRouteId = routeId; + return false; } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 135a467cc6b0..f49d51c70c1d 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5617,7 +5617,8 @@ public class NotificationManagerService extends SystemService { return !isCompatChangeEnabled || isCallerSystemOrSystemUi() || hasCompanionDevice(callingPkg, UserHandle.getUserId(callingUid), - AssociationRequest.DEVICE_PROFILE_WATCH); + Set.of(AssociationRequest.DEVICE_PROFILE_WATCH, + AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)); } private void enforcePolicyAccess(String pkg, String method) { @@ -10800,7 +10801,7 @@ public class NotificationManagerService extends SystemService { } private boolean hasCompanionDevice(String pkg, @UserIdInt int userId, - @Nullable @AssociationRequest.DeviceProfile String withDeviceProfile) { + @Nullable Set</* @AssociationRequest.DeviceProfile */ String> withDeviceProfiles) { if (mCompanionManager == null) { mCompanionManager = getCompanionManager(); } @@ -10812,7 +10813,7 @@ public class NotificationManagerService extends SystemService { try { List<AssociationInfo> associations = mCompanionManager.getAssociations(pkg, userId); for (AssociationInfo association : associations) { - if (withDeviceProfile == null || withDeviceProfile.equals( + if (withDeviceProfiles == null || withDeviceProfiles.contains( association.getDeviceProfile())) { return true; } 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..51ba3d45a7bf 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); } @@ -2858,14 +2860,17 @@ final class InstallPackageHelper { mPm.notifyPackageChanged(packageName, request.getAppId()); } - for (int userId : firstUserIds) { - // Apply restricted settings on potentially dangerous packages. Needs to happen - // after appOpsManager is notified of the new package - if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE - || request.getPackageSource() - == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) { - enableRestrictedSettings(packageName, request.getAppId(), userId); - } + // Apply restricted settings on potentially dangerous packages. Needs to happen + // after appOpsManager is notified of the new package + if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE + || request.getPackageSource() + == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) { + final int appId = request.getAppId(); + mPm.mHandler.post(() -> { + for (int userId : firstUserIds) { + enableRestrictedSettings(packageName, appId, userId); + } + }); } // Log current value of "unknown sources" setting @@ -3680,6 +3685,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/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 e8b54d58c907..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) { 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/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 4929df8061b2..eed46fee1ae1 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -36,6 +36,7 @@ import static com.android.window.flags.Flags.balShowToastsBlocked; import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS; import static java.lang.annotation.RetentionPolicy.SOURCE; +import static java.util.Objects.requireNonNull; import android.annotation.IntDef; import android.annotation.NonNull; @@ -152,36 +153,25 @@ public class BackgroundActivityStartController { static final int BAL_ALLOW_SDK_SANDBOX = 10; static String balCodeToString(@BalCode int balCode) { - switch (balCode) { - case BAL_ALLOW_ALLOWLISTED_COMPONENT: - return "BAL_ALLOW_ALLOWLISTED_COMPONENT"; - case BAL_ALLOW_ALLOWLISTED_UID: - return "BAL_ALLOW_ALLOWLISTED_UID"; - case BAL_ALLOW_DEFAULT: - return "BAL_ALLOW_DEFAULT"; - case BAL_ALLOW_FOREGROUND: - return "BAL_ALLOW_FOREGROUND"; - case BAL_ALLOW_GRACE_PERIOD: - return "BAL_ALLOW_GRACE_PERIOD"; - case BAL_ALLOW_PENDING_INTENT: - return "BAL_ALLOW_PENDING_INTENT"; - case BAL_ALLOW_PERMISSION: - return "BAL_ALLOW_PERMISSION"; - case BAL_ALLOW_SAW_PERMISSION: - return "BAL_ALLOW_SAW_PERMISSION"; - case BAL_ALLOW_SDK_SANDBOX: - return "BAL_ALLOW_SDK_SANDBOX"; - case BAL_ALLOW_VISIBLE_WINDOW: - return "BAL_ALLOW_VISIBLE_WINDOW"; - case BAL_BLOCK: - return "BAL_BLOCK"; - default: - throw new IllegalArgumentException("Unexpected value: " + balCode); - } + return switch (balCode) { + case BAL_ALLOW_ALLOWLISTED_COMPONENT -> "BAL_ALLOW_ALLOWLISTED_COMPONENT"; + case BAL_ALLOW_ALLOWLISTED_UID -> "BAL_ALLOW_ALLOWLISTED_UID"; + case BAL_ALLOW_DEFAULT -> "BAL_ALLOW_DEFAULT"; + case BAL_ALLOW_FOREGROUND -> "BAL_ALLOW_FOREGROUND"; + case BAL_ALLOW_GRACE_PERIOD -> "BAL_ALLOW_GRACE_PERIOD"; + case BAL_ALLOW_PENDING_INTENT -> "BAL_ALLOW_PENDING_INTENT"; + case BAL_ALLOW_PERMISSION -> "BAL_ALLOW_PERMISSION"; + case BAL_ALLOW_SAW_PERMISSION -> "BAL_ALLOW_SAW_PERMISSION"; + case BAL_ALLOW_SDK_SANDBOX -> "BAL_ALLOW_SDK_SANDBOX"; + case BAL_ALLOW_VISIBLE_WINDOW -> "BAL_ALLOW_VISIBLE_WINDOW"; + case BAL_BLOCK -> "BAL_BLOCK"; + default -> throw new IllegalArgumentException("Unexpected value: " + balCode); + }; } @GuardedBy("mService.mGlobalLock") - private HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity = new HashMap<>(); + private final HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity = + new HashMap<>(); @GuardedBy("mService.mGlobalLock") private FinishedActivityEntry mTopFinishedActivity = null; @@ -467,9 +457,8 @@ public class BackgroundActivityStartController { return !blocks(); } - BalVerdict setOnlyCreatorAllows(boolean onlyCreatorAllows) { + void setOnlyCreatorAllows(boolean onlyCreatorAllows) { mOnlyCreatorAllows = onlyCreatorAllows; - return this; } boolean onlyCreatorAllows() { @@ -481,10 +470,6 @@ public class BackgroundActivityStartController { return this; } - private boolean isBasedOnRealCaller() { - return mBasedOnRealCaller; - } - public String toString() { StringBuilder builder = new StringBuilder(); builder.append(balCodeToString(mCode)); @@ -583,15 +568,14 @@ public class BackgroundActivityStartController { BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state); if (!state.hasRealCaller()) { - BalVerdict resultForRealCaller = null; // nothing to compute if (resultForCaller.allows()) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Background activity start allowed. " - + state.dump(resultForCaller, resultForRealCaller)); + + state.dump(resultForCaller, resultForCaller)); } return statsLog(resultForCaller, state); } - return abortLaunch(state, resultForCaller, resultForRealCaller); + return abortLaunch(state, resultForCaller, resultForCaller); } // The realCaller result is only calculated for PendingIntents (indicated by a valid @@ -653,7 +637,7 @@ public class BackgroundActivityStartController { + " if the PI creator upgrades target_sdk to 35+" + " AND the PI sender upgrades target_sdk to 34+! " + state.dump(resultForCaller, resultForRealCaller)); - showBalRiskToast("BAL would be blocked", state); + showBalRiskToast(); // return the realCaller result for backwards compatibility return statsLog(resultForRealCaller, state); } @@ -679,7 +663,7 @@ public class BackgroundActivityStartController { + " if the PI creator upgrades target_sdk to 35+! " + " (missing opt in by PI creator)! " + state.dump(resultForCaller, resultForRealCaller)); - showBalRiskToast("BAL would be blocked", state); + showBalRiskToast(); return statsLog(resultForCaller, state); } Slog.wtf(TAG, @@ -696,7 +680,7 @@ public class BackgroundActivityStartController { + " if the PI sender upgrades target_sdk to 34+! " + " (missing opt in by PI sender)! " + state.dump(resultForCaller, resultForRealCaller)); - showBalRiskToast("BAL would be blocked", state); + showBalRiskToast(); return statsLog(resultForRealCaller, state); } Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed" @@ -712,7 +696,7 @@ public class BackgroundActivityStartController { BalVerdict resultForRealCaller) { Slog.w(TAG, "Background activity launch blocked! " + state.dump(resultForCaller, resultForRealCaller)); - showBalBlockedToast("BAL blocked", state); + showBalBlockedToast(); return statsLog(BalVerdict.BLOCK, state); } @@ -910,7 +894,7 @@ public class BackgroundActivityStartController { /** * Check if the app allows BAL. - * + * <p> * See {@link BackgroundLaunchProcessController#areBackgroundActivityStartsAllowed(int, int, * String, int, boolean, boolean, boolean, long, long, long)} for details on the * exceptions. @@ -1104,19 +1088,15 @@ public class BackgroundActivityStartController { return true; } - private void showBalBlockedToast(String toastText, BalState state) { + private void showBalBlockedToast() { if (balShowToastsBlocked()) { - showToast(toastText - + " caller:" + state.mCallingPackage - + " realCaller:" + state.mRealCallingPackage); + showToast("BAL blocked. go/debug-bal"); } } - private void showBalRiskToast(String toastText, BalState state) { + private void showBalRiskToast() { if (balShowToasts()) { - showToast(toastText - + " caller:" + state.mCallingPackage - + " realCaller:" + state.mRealCallingPackage); + showToast("BAL allowed in compat mode. go/debug-bal"); } } @@ -1281,7 +1261,7 @@ public class BackgroundActivityStartController { * 2. Or top of an adjacent task fragment to (1) * <p> * The 'sourceRecord' can be considered top even if it is 'finishing' - * + * <p> * Returns a class where the elements are: * <pre> * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into @@ -1344,7 +1324,7 @@ public class BackgroundActivityStartController { /** * Determines if a source is allowed to add or remove activities from the task, * if the current ActivityRecord is above it in the stack - * + * <p> * A transition is blocked ({@code false} returned) if all of the following are met: * <pre> * 1. The source activity and the current activity record belong to different apps @@ -1489,8 +1469,8 @@ public class BackgroundActivityStartController { if (code == BAL_ALLOW_PENDING_INTENT && (callingUid == Process.SYSTEM_UID || realCallingUid == Process.SYSTEM_UID)) { - String activityName = - intent != null ? intent.getComponent().flattenToShortString() : ""; + String activityName = intent != null + ? requireNonNull(intent.getComponent()).flattenToShortString() : ""; FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED, activityName, BAL_ALLOW_PENDING_INTENT, 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/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 9c21e4c0121e..ec4bdf91bcca 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -202,7 +202,6 @@ import android.os.Build; import android.os.Debug; import android.os.IBinder; import android.os.PowerManager; -import android.os.PowerManager.WakeReason; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; @@ -702,11 +701,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ private final Region mTapExcludeRegion = new Region(); - /** - * Used for testing because the real PowerManager is final. - */ - private PowerManagerWrapper mPowerManagerWrapper; - private static final StringBuilder sTmpSB = new StringBuilder(); /** @@ -1061,34 +1055,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mOnBackInvokedCallbackInfo; } - interface PowerManagerWrapper { - void wakeUp(long time, @WakeReason int reason, String details); - - boolean isInteractive(); - - } - WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility, int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow) { - this(service, s, c, token, parentWindow, appOp, a, viewVisibility, ownerId, showUserId, - ownerCanAddInternalSystemWindow, new PowerManagerWrapper() { - @Override - public void wakeUp(long time, @WakeReason int reason, String details) { - service.mPowerManager.wakeUp(time, reason, details); - } - - @Override - public boolean isInteractive() { - return service.mPowerManager.isInteractive(); - } - }); - } - - WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, - WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility, - int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow, - PowerManagerWrapper powerManagerWrapper) { super(service); mTmpTransaction = service.mTransactionFactory.get(); mSession = s; @@ -1106,7 +1075,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mViewVisibility = viewVisibility; mPolicy = mWmService.mPolicy; mContext = mWmService.mContext; - mPowerManagerWrapper = powerManagerWrapper; mForceSeamlesslyRotate = token.mRoundedCornerOverlay; mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle( mActivityRecord != null @@ -2831,12 +2799,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean canTurnScreenOn = mActivityRecord == null || mActivityRecord.currentLaunchCanTurnScreenOn(); if (allowTheaterMode && canTurnScreenOn - && (mWmService.mAtmService.isDreaming() - || !mPowerManagerWrapper.isInteractive())) { + && (mWmService.mAtmService.isDreaming() + || !mWmService.mPowerManager.isInteractive())) { if (DEBUG_VISIBILITY || DEBUG_POWER) { Slog.v(TAG, "Relayout window turning screen on: " + this); } - mPowerManagerWrapper.wakeUp(SystemClock.uptimeMillis(), + mWmService.mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_APPLICATION, "android.server.wm:SCREEN_ON_FLAG"); } 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 adbd3c9c5096..3cbceec5b9cd 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -631,8 +631,8 @@ <xs:annotation name="nonnull"/> <xs:annotation name="final"/> </xs:element> - <xs:element name="mode" type="xs:string" minOccurs="0"/> - <xs:element name="setting" type="xs:string" minOccurs="0"/> + <xs:element name="mode" type="AutoBrightnessModeName" minOccurs="0"/> + <xs:element name="setting" type="AutoBrightnessSettingName" minOccurs="0"/> </xs:complexType> <!-- Represents a point in the display brightness mapping, representing the lux level from the @@ -765,4 +765,23 @@ </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> + + <!-- Predefined auto-brighntess settings --> + <xs:simpleType name="AutoBrightnessSettingName"> + <xs:restriction base="xs:string"> + <xs:enumeration value="dim"/> + <xs:enumeration value="normal"/> + <xs:enumeration value="bright"/> + </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 98c95edef237..79ea274e2fca 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -16,6 +16,20 @@ package com.android.server.display.config { method public void setEnabled(boolean); } + 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 enum AutoBrightnessSettingName { + method public String getRawName(); + enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName bright; + enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName dim; + enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName normal; + } + public class BlockingZoneConfig { ctor public BlockingZoneConfig(); method public final com.android.server.display.config.BlockingZoneThreshold getBlockingZoneThreshold(); @@ -219,11 +233,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 String getMode(); - method public String getSetting(); + method public com.android.server.display.config.AutoBrightnessModeName getMode(); + method public com.android.server.display.config.AutoBrightnessSettingName getSetting(); method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap); - method public void setMode(String); - method public void setSetting(String); + method public void setMode(com.android.server.display.config.AutoBrightnessModeName); + method public void setSetting(com.android.server.display.config.AutoBrightnessSettingName); } 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 c5a1ba1f6758..fb73aff44c64 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.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 static org.junit.Assert.assertEquals; @@ -29,21 +30,22 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import android.content.res.Resources; import android.content.res.TypedArray; import android.hardware.display.BrightnessConfiguration; import android.os.PowerManager; +import android.provider.Settings; +import android.testing.TestableContext; import android.util.MathUtils; import android.util.Spline; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import com.android.server.display.whitebalance.DisplayWhiteBalanceController; - +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import java.util.Arrays; @@ -154,15 +156,23 @@ public class BrightnessMappingStrategyTest { private static final float TOLERANCE = 0.0001f; - @Mock - DisplayWhiteBalanceController mMockDwbc; + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + + @Before + public void setUp() { + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_FOR_ALS, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL); + } @Test public void testSimpleStrategyMappingAtControlPoints() { - Resources res = createResources(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build(); - BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); 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); @@ -171,10 +181,10 @@ public class BrightnessMappingStrategyTest { @Test public void testSimpleStrategyMappingBetweenControlPoints() { - Resources res = createResources(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build(); - BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); 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; @@ -186,10 +196,10 @@ public class BrightnessMappingStrategyTest { @Test public void testSimpleStrategyIgnoresNewConfiguration() { - Resources res = createResources(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build(); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); final float[] lux = { 0f, 1f }; final float[] nits = { 0, PowerManager.BRIGHTNESS_ON }; @@ -202,10 +212,10 @@ public class BrightnessMappingStrategyTest { @Test public void testSimpleStrategyIgnoresNullConfiguration() { - Resources res = createResources(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build(); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); strategy.setBrightnessConfiguration(null); final int n = DISPLAY_LEVELS.length; @@ -216,11 +226,11 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyMappingAtControlPoints() { - Resources res = createResources(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS) .build(); - BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); assertNotNull("BrightnessMappingStrategy should not be null", physical); for (int i = 0; i < LUX_LEVELS.length; i++) { final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1], @@ -235,11 +245,11 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyMappingBetweenControlPoints() { - Resources res = createResources(); + setUpResources(); 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); + BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); assertNotNull("BrightnessMappingStrategy should not be null", physical); Spline brightnessToNits = Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS); @@ -254,11 +264,11 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyUsesNewConfigurations() { - Resources res = createResources(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS) .build(); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); final float[] lux = {0f, 1f}; final float[] nits = { @@ -281,11 +291,11 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyRecalculateSplines() { - Resources res = createResources(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS) .build(); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length]; for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) { adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f; @@ -326,12 +336,12 @@ public class BrightnessMappingStrategyTest { @Test public void testDefaultStrategyIsPhysical() { - Resources res = createResources(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS) .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS) .build(); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy); } @@ -342,19 +352,19 @@ public class BrightnessMappingStrategyTest { float tmp = lux[idx]; lux[idx] = lux[idx + 1]; lux[idx + 1] = tmp; - Resources res = createResources(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux) .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build(); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); assertNull(strategy); // And make sure we get the same result even if it's monotone but not increasing. lux[idx] = lux[idx + 1]; ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux) .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build(); - strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, - mMockDwbc); + strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, + /* displayWhiteBalanceController= */ null); assertNull(strategy); } @@ -365,74 +375,74 @@ 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(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux) .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build(); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); assertNull(strategy); ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux) .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS) .setAutoBrightnessLevels(DISPLAY_LEVELS).build(); - strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, - mMockDwbc); + strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, + /* displayWhiteBalanceController= */ null); assertNull(strategy); // Extra backlight level final float[] backlight = Arrays.copyOf(DISPLAY_LEVELS, DISPLAY_LEVELS.length + 1); backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1; - res = createResources(); + setUpResources(); ddc = new DdcBuilder().setAutoBrightnessLevels(backlight).build(); - strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, - mMockDwbc); + strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, + /* displayWhiteBalanceController= */ null); assertNull(strategy); // 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(); + setUpResources(); ddc = new DdcBuilder().setAutoBrightnessLevelsNits(nits) .setAutoBrightnessLevels(EMPTY_FLOAT_ARRAY).build(); - strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, - mMockDwbc); + strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT, + /* displayWhiteBalanceController= */ null); assertNull(strategy); } @Test public void testPhysicalStrategyRequiresNitsMapping() { - Resources res = createResources(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setNitsRange(EMPTY_FLOAT_ARRAY).build(); - BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); assertNull(physical); } @Test public void testStrategiesAdaptToUserDataPoint() { - Resources res = createResources(); + setUpResources(); 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)); + assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null)); 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)); + setUpResources(); + assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null)); } @Test public void testIdleModeConfigLoadsCorrectly() { - Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE); + setUpResources(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. - BrightnessMappingStrategy bms = BrightnessMappingStrategy.create(res, ddc, + BrightnessMappingStrategy bms = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_IDLE, - mMockDwbc); + /* displayWhiteBalanceController= */ null); assertNotNull("BrightnessMappingStrategy should not be null", bms); // Ensure that the config is the one we set @@ -500,36 +510,31 @@ public class BrightnessMappingStrategyTest { assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/); } - private Resources createResources() { - return createResources(EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY); + private void setUpResources() { + setUpResources(EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY); } - private Resources createResources(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) { - - Resources mockResources = mock(Resources.class); + private void setUpResources(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) { if (luxLevelsIdle.length > 0) { int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1, luxLevelsIdle.length); - when(mockResources.getIntArray( - com.android.internal.R.array.config_autoBrightnessLevelsIdle)) - .thenReturn(luxLevelsIdleResource); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.array.config_autoBrightnessLevelsIdle, + luxLevelsIdleResource); } TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle); - when(mockResources.obtainTypedArray( - com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle)) - .thenReturn(mockBrightnessLevelNitsIdle); - - when(mockResources.getInteger( - com.android.internal.R.integer.config_screenBrightnessSettingMinimum)) - .thenReturn(1); - when(mockResources.getInteger( - com.android.internal.R.integer.config_screenBrightnessSettingMaximum)) - .thenReturn(255); - when(mockResources.getFraction( - com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1)) - .thenReturn(MAXIMUM_GAMMA); - return mockResources; + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle, + mockBrightnessLevelNitsIdle); + + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.integer.config_screenBrightnessSettingMinimum, 1); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.integer.config_screenBrightnessSettingMaximum, 255); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, + MAXIMUM_GAMMA); } private TypedArray createFloatTypedArray(float[] vals) { @@ -570,11 +575,11 @@ public class BrightnessMappingStrategyTest { final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3); - Resources resources = createResources(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX) .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build(); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); // Let's start with a validity check: assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */); assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */); @@ -600,11 +605,11 @@ 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(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX) .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build(); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); // Validity check: assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */); assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */); @@ -627,11 +632,11 @@ 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(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX) .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build(); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f); strategy.addUserDataPoint(/* lux= */ 2500, /* brightness= */ 1.0f); assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f); @@ -650,11 +655,11 @@ 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(); + setUpResources(); DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX) .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build(); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc, - AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null); // Validity, as per tradition: assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */); assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */); @@ -679,15 +684,33 @@ public class BrightnessMappingStrategyTest { @Test public void testGetMode() { - Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE); + setUpResources(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); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc, + AUTO_BRIGHTNESS_MODE_IDLE, /* displayWhiteBalanceController= */ null); assertEquals(AUTO_BRIGHTNESS_MODE_IDLE, strategy.getMode()); } + @Test + public void testAutoBrightnessModeAndPreset() { + int mode = AUTO_BRIGHTNESS_MODE_DOZE; + int preset = Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM; + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_FOR_ALS, preset); + + setUpResources(); + DisplayDeviceConfig ddc = new DdcBuilder() + .setAutoBrightnessLevels(mode, preset, DISPLAY_LEVELS) + .setAutoBrightnessLevelsLux(mode, preset, LUX_LEVELS).build(); + BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc, mode, + /* displayWhiteBalanceController= */ null); + 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); + } + } + private static class DdcBuilder { private DisplayDeviceConfig mDdc; @@ -695,9 +718,12 @@ public class BrightnessMappingStrategyTest { mDdc = mock(DisplayDeviceConfig.class); when(mDdc.getNits()).thenReturn(DISPLAY_RANGE_NITS); when(mDdc.getBrightness()).thenReturn(DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT); - when(mDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS); + when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL)).thenReturn(LUX_LEVELS); when(mDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY); - when(mDdc.getAutoBrightnessBrighteningLevels()).thenReturn(EMPTY_FLOAT_ARRAY); + when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL)) + .thenReturn(EMPTY_FLOAT_ARRAY); } DdcBuilder setNitsRange(float[] nitsArray) { @@ -711,7 +737,15 @@ public class BrightnessMappingStrategyTest { } DdcBuilder setAutoBrightnessLevelsLux(float[] luxLevels) { - when(mDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevels); + when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL)).thenReturn(luxLevels); + return this; + } + + DdcBuilder setAutoBrightnessLevelsLux( + @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset, + float[] luxLevels) { + when(mDdc.getAutoBrightnessBrighteningLevelsLux(mode, preset)).thenReturn(luxLevels); return this; } @@ -721,7 +755,17 @@ public class BrightnessMappingStrategyTest { } DdcBuilder setAutoBrightnessLevels(float[] brightnessLevels) { - when(mDdc.getAutoBrightnessBrighteningLevels()).thenReturn(brightnessLevels); + when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL)) + .thenReturn(brightnessLevels); + return this; + } + + DdcBuilder setAutoBrightnessLevels( + @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset, + float[] brightnessLevels) { + when(mDdc.getAutoBrightnessBrighteningLevels(mode, preset)) + .thenReturn(brightnessLevels); return this; } 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 a4c15b5c5725..7a84406f1b08 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -18,6 +18,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.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; 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; @@ -40,6 +42,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.hardware.display.DisplayManagerInternal; import android.os.Temperature; +import android.provider.Settings; import android.util.SparseArray; import android.util.Spline; import android.view.SurfaceControl; @@ -605,13 +608,15 @@ 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.getAutoBrightnessBrighteningLevels(), new - float[]{brightnessIntToFloat(50), brightnessIntToFloat(100), - brightnessIntToFloat(150)}, SMALL_DELTA); + assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), + new float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA); + assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux( + AUTO_BRIGHTNESS_MODE_DEFAULT, Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), + new float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA); + assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels( + AUTO_BRIGHTNESS_MODE_DEFAULT, Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), + new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100), + brightnessIntToFloat(150)}, SMALL_DELTA); // Test thresholds assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA); @@ -737,30 +742,40 @@ public final class DisplayDeviceConfigTest { getValidProxSensor(), /* includeIdleMode= */ false)); assertArrayEquals(new float[]{0.0f, 80}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux( + AUTO_BRIGHTNESS_MODE_DEFAULT, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA); assertArrayEquals(new float[]{0.2f, 0.3f}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels( + AUTO_BRIGHTNESS_MODE_DEFAULT, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA); assertArrayEquals(new float[]{0.0f, 90}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("default", "dim"), - ZERO_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux( + AUTO_BRIGHTNESS_MODE_DEFAULT, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), ZERO_DELTA); assertArrayEquals(new float[]{0.3f, 0.4f}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("default", "dim"), - SMALL_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels( + AUTO_BRIGHTNESS_MODE_DEFAULT, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), SMALL_DELTA); assertArrayEquals(new float[]{0.0f, 95}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "normal"), - ZERO_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux( + AUTO_BRIGHTNESS_MODE_DOZE, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA); assertArrayEquals(new float[]{0.35f, 0.45f}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "normal"), - SMALL_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels( + AUTO_BRIGHTNESS_MODE_DOZE, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA); assertArrayEquals(new float[]{0.0f, 100}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "bright"), - ZERO_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux( + AUTO_BRIGHTNESS_MODE_DOZE, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), ZERO_DELTA); assertArrayEquals(new float[]{0.4f, 0.5f}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "bright"), - SMALL_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels( + AUTO_BRIGHTNESS_MODE_DOZE, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), SMALL_DELTA); } @Test @@ -772,9 +787,13 @@ public final class DisplayDeviceConfigTest { assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100), brightnessIntToFloat(150)}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels( + AUTO_BRIGHTNESS_MODE_DEFAULT, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA); assertArrayEquals(new float[]{0, 110, 500}, - mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA); + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux( + AUTO_BRIGHTNESS_MODE_DEFAULT, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA); assertArrayEquals(new float[]{2, 200, 600}, mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), SMALL_DELTA); } 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..7a708487293e 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; @@ -773,7 +775,7 @@ public final class DisplayPowerController2Test { final float hdrBrightness = 0.3f; when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); - when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true); + when(mHolder.automaticBrightnessController.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE); when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness( any(BrightnessEvent.class))).thenReturn(sdrBrightness); when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f); @@ -1232,7 +1234,7 @@ public final class DisplayPowerController2Test { float brightness = 0.6f; when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); - when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true); + when(mHolder.automaticBrightnessController.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE); when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness( any(BrightnessEvent.class))).thenReturn(brightness); @@ -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.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE); + + 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}. */ @@ -1853,7 +1905,7 @@ public final class DisplayPowerController2Test { } @Override - BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources, + BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context, DisplayDeviceConfig displayDeviceConfig, DisplayWhiteBalanceController displayWhiteBalanceController) { return mBrightnessMappingStrategy; diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 64cdac464720..d69c140716f3 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -1164,7 +1164,7 @@ public final class DisplayPowerControllerTest { float brightness = 0.6f; when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); - when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true); + when(mHolder.automaticBrightnessController.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE); when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness( any(BrightnessEvent.class))).thenReturn(brightness); @@ -1669,7 +1669,7 @@ public final class DisplayPowerControllerTest { } @Override - BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources, + BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context, DisplayDeviceConfig displayDeviceConfig, DisplayWhiteBalanceController displayWhiteBalanceController) { return mBrightnessMappingStrategy; 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/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/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/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index e004ca011a6b..884ea31504bc 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; @@ -13615,7 +13616,31 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) - public void setNotificationPolicy_watchCompanionApp_setsGlobalPolicy() throws RemoteException { + public void setNotificationPolicy_watchCompanionApp_setsGlobalPolicy() + throws RemoteException { + setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy( + AssociationRequest.DEVICE_PROFILE_WATCH, true); + } + + @Test + @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void setNotificationPolicy_autoCompanionApp_setsGlobalPolicy() + throws RemoteException { + setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy( + AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, true); + } + + @Test + @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void setNotificationPolicy_otherCompanionApp_doesNotSetGlobalPolicy() + throws RemoteException { + setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy( + AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, false); + } + + private void setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy( + @AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy) + throws RemoteException { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mService.setCallerIsNormalPackage(); ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); @@ -13625,14 +13650,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mCompanionMgr.getAssociations(anyString(), anyInt())) .thenReturn(ImmutableList.of( new AssociationInfo.Builder(1, mUserId, "package") - .setDisplayName("My watch") - .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH) + .setDisplayName("My connected device") + .setDeviceProfile(deviceProfile) .build())); NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); mBinderService.setNotificationPolicy("package", policy, false); - verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt()); + if (canSetGlobalPolicy) { + verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt()); + } else { + verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(), + eq(policy), anyInt()); + } } @Test @@ -13702,7 +13732,29 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) - public void setInterruptionFilter_watchCompanionApp_setsGlobalPolicy() throws RemoteException { + public void setInterruptionFilter_watchCompanionApp_setsGlobalZen() throws RemoteException { + setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen( + AssociationRequest.DEVICE_PROFILE_WATCH, true); + } + + @Test + @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void setInterruptionFilter_autoCompanionApp_setsGlobalZen() throws RemoteException { + setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen( + AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, true); + } + + @Test + @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void setInterruptionFilter_otherCompanionApp_doesNotSetGlobalZen() + throws RemoteException { + setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen( + AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, false); + } + + private void setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen( + @AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy) + throws RemoteException { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = zenModeHelper; @@ -13712,14 +13764,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mCompanionMgr.getAssociations(anyString(), anyInt())) .thenReturn(ImmutableList.of( new AssociationInfo.Builder(1, mUserId, "package") - .setDisplayName("My watch") - .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH) + .setDisplayName("My connected device") + .setDeviceProfile(deviceProfile) .build())); mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false); - verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null), - eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt()); + if (canSetGlobalPolicy) { + verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null), + eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt()); + } else { + verify(zenModeHelper).applyGlobalZenModeAsImplicitZenRule(anyString(), anyInt(), + eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS)); + } } @Test @@ -13737,6 +13794,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 @@ -13766,6 +13824,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); @@ -13793,6 +13852,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 @@ -13823,6 +13883,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 @@ -13851,6 +13912,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); @@ -13877,6 +13939,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/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/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 85c6f9e9b2fe..718c59875c1a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -202,8 +202,7 @@ public class ActivityRecordTests extends WindowTestsBase { } private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() { - return new TestStartingWindowOrganizer(mAtm, - mSystemServicesTestRule.getPowerManagerWrapper()); + return new TestStartingWindowOrganizer(mAtm); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 51f0404e2396..8cd9ff359111 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -134,7 +134,6 @@ public class SystemServicesTestRule implements TestRule { private StaticMockitoSession mMockitoSession; private ActivityTaskManagerService mAtmService; private WindowManagerService mWmService; - private WindowState.PowerManagerWrapper mPowerManagerWrapper; private InputManagerService mImService; private InputChannel mInputChannel; private Runnable mOnBeforeServicesCreated; @@ -360,7 +359,6 @@ public class SystemServicesTestRule implements TestRule { } private void setUpWindowManagerService() { - mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class); TestWindowManagerPolicy wmPolicy = new TestWindowManagerPolicy(); TestDisplayWindowSettingsProvider testDisplayWindowSettingsProvider = new TestDisplayWindowSettingsProvider(); @@ -485,10 +483,6 @@ public class SystemServicesTestRule implements TestRule { return mAtmService; } - WindowState.PowerManagerWrapper getPowerManagerWrapper() { - return mPowerManagerWrapper; - } - /** Creates a no-op wakelock object. */ PowerManager.WakeLock createStubbedWakeLock(boolean needVerification) { if (needVerification) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 2007f680f1ae..75e252f9a415 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -52,7 +52,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -368,28 +367,26 @@ public class WindowStateTests extends WindowTestsBase { firstWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; secondWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; - final WindowState.PowerManagerWrapper powerManagerWrapper = - mSystemServicesTestRule.getPowerManagerWrapper(); - reset(powerManagerWrapper); + final var powerManager = mWm.mPowerManager; + clearInvocations(powerManager); firstWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); - verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString()); + verify(powerManager).wakeUp(anyLong(), anyInt(), anyString()); - reset(powerManagerWrapper); + clearInvocations(powerManager); secondWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); - verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString()); + verify(powerManager).wakeUp(anyLong(), anyInt(), anyString()); } private void testPrepareWindowToDisplayDuringRelayout(WindowState appWindow, boolean expectedWakeupCalled, boolean expectedCurrentLaunchCanTurnScreenOn) { - final WindowState.PowerManagerWrapper powerManagerWrapper = - mSystemServicesTestRule.getPowerManagerWrapper(); - reset(powerManagerWrapper); + final var powerManager = mWm.mPowerManager; + clearInvocations(powerManager); appWindow.prepareWindowToDisplayDuringRelayout(false /* wasVisible */); if (expectedWakeupCalled) { - verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString()); + verify(powerManager).wakeUp(anyLong(), anyInt(), anyString()); } else { - verify(powerManagerWrapper, never()).wakeUp(anyLong(), anyInt(), anyString()); + verify(powerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); } // If wakeup is expected to be called, the currentLaunchCanTurnScreenOn should be false // because the state will be consumed. diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 616a23e7ab5b..a5f6190f2d51 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -637,14 +637,12 @@ class WindowTestsBase extends SystemServiceTestsBase { WindowState createWindow(WindowState parent, int type, WindowToken token, String name, int ownerId, boolean ownerCanAddInternalSystemWindow, IWindow iwindow) { return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId), - ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow, - mSystemServicesTestRule.getPowerManagerWrapper()); + ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow); } static WindowState createWindow(WindowState parent, int type, WindowToken token, String name, int ownerId, int userId, boolean ownerCanAddInternalSystemWindow, - WindowManagerService service, Session session, IWindow iWindow, - WindowState.PowerManagerWrapper powerManagerWrapper) { + WindowManagerService service, Session session, IWindow iWindow) { SystemServicesTestRule.checkHoldsLock(service.mGlobalLock); final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type); @@ -652,9 +650,7 @@ class WindowTestsBase extends SystemServiceTestsBase { attrs.packageName = "test"; final WindowState w = new WindowState(service, session, iWindow, token, parent, - OP_NONE, attrs, VISIBLE, ownerId, userId, - ownerCanAddInternalSystemWindow, - powerManagerWrapper); + OP_NONE, attrs, VISIBLE, ownerId, userId, ownerCanAddInternalSystemWindow); // TODO: Probably better to make this call in the WindowState ctor to avoid errors with // adding it to the token... token.addWindow(w); @@ -1738,17 +1734,14 @@ class WindowTestsBase extends SystemServiceTestsBase { static class TestStartingWindowOrganizer extends WindowOrganizerTests.StubOrganizer { private final ActivityTaskManagerService mAtm; private final WindowManagerService mWMService; - private final WindowState.PowerManagerWrapper mPowerManagerWrapper; private Runnable mRunnableWhenAddingSplashScreen; private final SparseArray<IBinder> mTaskAppMap = new SparseArray<>(); private final HashMap<IBinder, WindowState> mAppWindowMap = new HashMap<>(); - TestStartingWindowOrganizer(ActivityTaskManagerService service, - WindowState.PowerManagerWrapper powerManagerWrapper) { + TestStartingWindowOrganizer(ActivityTaskManagerService service) { mAtm = service; mWMService = mAtm.mWindowManager; - mPowerManagerWrapper = powerManagerWrapper; mAtm.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer(Runnable::run); mAtm.mTaskOrganizerController.registerTaskOrganizer(this); } @@ -1767,8 +1760,7 @@ class WindowTestsBase extends SystemServiceTestsBase { final WindowState window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, activity, "Starting window", 0 /* ownerId */, 0 /* userId*/, - false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow, - mPowerManagerWrapper); + false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow); activity.mStartingWindow = window; mAppWindowMap.put(info.appToken, window); mTaskAppMap.put(info.taskInfo.taskId, info.appToken); diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index 4c978ad01a81..2445f51a4241 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -28,6 +28,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.usage.ExternalStorageStats; +import android.app.usage.Flags; import android.app.usage.IStorageStatsManager; import android.app.usage.StorageStats; import android.app.usage.UsageStatsManagerInternal; @@ -434,6 +435,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub { final long[] ceDataInodes = new long[packageNames.length]; String[] codePaths = new String[0]; + final PackageStats stats = new PackageStats(TAG); for (int i = 0; i < packageNames.length; i++) { try { final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageNames[i], @@ -443,7 +445,11 @@ public class StorageStatsService extends IStorageStatsManager.Stub { } else { if (appInfo.getCodePath() != null) { codePaths = ArrayUtils.appendElement(String.class, codePaths, - appInfo.getCodePath()); + appInfo.getCodePath()); + } + if (Flags.getAppBytesByDataTypeApi()) { + computeAppStatsByDataTypes( + stats, appInfo.sourceDir); } } } catch (NameNotFoundException e) { @@ -451,7 +457,6 @@ public class StorageStatsService extends IStorageStatsManager.Stub { } } - final PackageStats stats = new PackageStats(TAG); try { mInstaller.getAppSize(volumeUuid, packageNames, userId, getDefaultFlags(), appId, ceDataInodes, codePaths, stats); @@ -587,6 +592,9 @@ public class StorageStatsService extends IStorageStatsManager.Stub { res.codeBytes = stats.codeSize + stats.externalCodeSize; res.dataBytes = stats.dataSize + stats.externalDataSize; res.cacheBytes = stats.cacheSize + stats.externalCacheSize; + res.apkBytes = stats.apkSize; + res.libBytes = stats.libSize; + res.dmBytes = stats.dmSize; res.externalCacheBytes = stats.externalCacheSize; return res; } @@ -894,4 +902,61 @@ public class StorageStatsService extends IStorageStatsManager.Stub { mStorageStatsAugmenters.add(Pair.create(tag, storageStatsAugmenter)); } } + + private long getDirBytes(File dir) { + if (!dir.isDirectory()) { + return 0; + } + + long size = 0; + try { + for (File file : dir.listFiles()) { + if (file.isFile()) { + size += file.length(); + continue; + } + if (file.isDirectory()) { + size += getDirBytes(file); + } + } + } catch (NullPointerException e) { + Slog.w(TAG, "Failed to list directory " + dir.getName()); + } + + return size; + } + + private long getFileBytesInDir(File dir, String suffix) { + if (!dir.isDirectory()) { + return 0; + } + + long size = 0; + try { + for (File file : dir.listFiles()) { + if (file.isFile() && file.getName().endsWith(suffix)) { + size += file.length(); + } + } + } catch (NullPointerException e) { + Slog.w(TAG, "Failed to list directory " + dir.getName()); + } + + return size; + } + + private void computeAppStatsByDataTypes( + PackageStats stats, String sourceDirName) { + + // Get apk, lib, dm file sizes. + File srcDir = new File(sourceDirName); + if (srcDir.isFile()) { + sourceDirName = srcDir.getParent(); + srcDir = new File(sourceDirName); + } + + stats.apkSize += getFileBytesInDir(srcDir, ".apk"); + stats.dmSize += getFileBytesInDir(srcDir, ".dm"); + stats.libSize += getDirBytes(new File(sourceDirName + "/lib/")); + } } 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 101d285bc92c..c7b84a3b9530 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -3721,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 @@ -3754,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 @@ -3776,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). * @@ -3800,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 @@ -3824,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). * @@ -8891,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 = @@ -10050,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; @@ -10565,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, ""); @@ -10837,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. * |