summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Notification.java35
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig10
-rw-r--r--core/java/android/app/notification.aconfig11
-rw-r--r--core/java/android/content/pm/PackageManager.java10
-rw-r--r--core/java/android/hardware/biometrics/BiometricFingerprintConstants.java11
-rw-r--r--core/res/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt74
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt74
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt63
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/DesktopModeUtils.kt112
-rw-r--r--location/Android.bp1
-rw-r--r--location/api/current.txt6
-rw-r--r--location/api/system-current.txt10
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.pngbin79257 -> 93684 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.pngbin91931 -> 96262 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.pngbin62863 -> 64102 bytes
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt27
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt6
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt15
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt14
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt7
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt13
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java24
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java37
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java11
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java19
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java205
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java10
-rw-r--r--services/core/java/com/android/server/wm/Task.java8
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java63
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java87
47 files changed, 888 insertions, 258 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6ff1bfc5291c..e39928b5e091 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3090,6 +3090,25 @@ public class Notification implements Parcelable
}
/**
+ * @hide
+ */
+ public int loadHeaderAppIconRes(Context context) {
+ ApplicationInfo info = null;
+ if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
+ info = extras.getParcelable(
+ EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo.class);
+ }
+ if (info == null) {
+ info = context.getApplicationInfo();
+ }
+ if (info != null) {
+ return info.icon;
+ }
+ return 0;
+ }
+
+ /**
* Removes heavyweight parts of the Notification object for archival or for sending to
* listeners when the full contents are not necessary.
* @hide
@@ -5963,12 +5982,21 @@ public class Notification implements Parcelable
}
private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
- if (mN.mSmallIcon == null && mN.icon != 0) {
+ if (Flags.notificationsUseAppIcon()) {
+ // Override small icon with app icon
+ mN.mSmallIcon = Icon.createWithResource(mContext,
+ mN.loadHeaderAppIconRes(mContext));
+ } else if (mN.mSmallIcon == null && mN.icon != 0) {
mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
}
+
contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
- processSmallIconColor(mN.mSmallIcon, contentView, p);
+
+ // Don't change color if we're using the app icon.
+ if (!Flags.notificationsUseAppIcon()) {
+ processSmallIconColor(mN.mSmallIcon, contentView, p);
+ }
}
/**
@@ -6804,7 +6832,8 @@ public class Notification implements Parcelable
*/
private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
StandardTemplateParams p) {
- boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
+ boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext,
+ smallIcon);
int color = getSmallIconColor(p);
contentView.setInt(R.id.icon, "setBackgroundColor",
getBackgroundColor(p));
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index ac843cbfbfac..f5e0f684c5db 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -282,3 +282,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "headless_single_user_fixes"
+ namespace: "enterprise"
+ description: "Various fixes for headless single user mode"
+ bug: "289515470"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 250953e61137..28afd5f63435 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -1,5 +1,9 @@
package: "android.app"
+# Note: When adding a new flag here, consider including the word "notification(s)" in the flag name
+# when appropriate, as it's not currently part of the namespace so it may not be obvious what the
+# flag relates to.
+
flag {
name: "modes_api"
is_exported: true
@@ -41,6 +45,13 @@ flag {
}
flag {
+ name: "notifications_use_app_icon"
+ namespace: "systemui"
+ description: "Experiment to replace the small icon in a notification with the app icon."
+ bug: "335211019"
+}
+
+flag {
name: "keyguard_private_notifications"
namespace: "systemui"
description: "Fixes the behavior of KeyguardManager#setPrivateNotificationsAllowed()"
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4c0da7c98488..f5bff9dc1d0e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4844,6 +4844,16 @@ public abstract class PackageManager {
public static final String FEATURE_ROTARY_ENCODER_LOW_RES =
"android.hardware.rotaryencoder.lowres";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * support for contextual search helper.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CONTEXTUAL_SEARCH_HELPER =
+ "android.software.contextualsearch";
+
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index 770448bd594b..fc72db3c5791 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -221,7 +221,8 @@ public interface BiometricFingerprintConstants {
FINGERPRINT_ACQUIRED_IMMOBILE,
FINGERPRINT_ACQUIRED_TOO_BRIGHT,
FINGERPRINT_ACQUIRED_POWER_PRESSED,
- FINGERPRINT_ACQUIRED_RE_ENROLL})
+ FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL,
+ FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED})
@Retention(RetentionPolicy.SOURCE)
@interface FingerprintAcquired {}
@@ -316,7 +317,13 @@ public interface BiometricFingerprintConstants {
* This message is sent to encourage the user to re-enroll their fingerprints.
* @hide
*/
- int FINGERPRINT_ACQUIRED_RE_ENROLL = 12;
+ int FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL = 12;
+
+ /**
+ * This message is sent to force the user to re-enroll their fingerprints.
+ * @hide
+ */
+ int FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED = 13;
/**
* @hide
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4c941fda1f14..f31d390d376c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5188,7 +5188,7 @@
<string name="lock_to_app_unlock_password">Ask for password before unpinning</string>
<!-- Notification shown when device owner silently installs a package [CHAR LIMIT=NONE] -->
- <string name="package_installed_device_owner">Installed by your admin</string>
+ <string name="package_installed_device_owner">Installed by your admin.\nGo to settings to view granted permissions</string>
<!-- Notification shown when device owner silently updates a package [CHAR LIMIT=NONE] -->
<string name="package_updated_device_owner">Updated by your admin</string>
<!-- Notification shown when device owner silently deletes a package [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS
new file mode 100644
index 000000000000..73a5a23909c5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS
@@ -0,0 +1,5 @@
+# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Freeform
+# Bug component: 929241
+
+uysalorhan@google.com
+pragyabajoria@google.com \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt
new file mode 100644
index 000000000000..4c781d36acf6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.AssertionInvocationGroup
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
+import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
+import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
+import android.tools.flicker.config.AssertionTemplates
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerConfigEntry
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.config.ScenarioId
+import android.tools.flicker.config.desktopmode.Components
+import android.tools.flicker.extractors.ITransitionMatcher
+import android.tools.flicker.extractors.ShellTransitionScenarioExtractor
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.traces.wm.Transition
+import android.tools.traces.wm.TransitionType
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterDesktopWithDragLandscape : EnterDesktopWithDrag(Rotation.ROTATION_90) {
+ @ExpectedScenarios(["END_DRAG_TO_DESKTOP"]) @Test override fun enterDesktopWithDrag() =
+ super.enterDesktopWithDrag()
+
+ companion object {
+ private val END_DRAG_TO_DESKTOP = FlickerConfigEntry(
+ scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"),
+ extractor = ShellTransitionScenarioExtractor(
+ transitionMatcher = object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions.filter {
+ it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP}
+ }
+ }),
+ assertions = AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
+ AppWindowHasDesktopModeInitialBoundsAtTheEnd(Components.DESKTOP_MODE_APP)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
+
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(END_DRAG_TO_DESKTOP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt
new file mode 100644
index 000000000000..d99d875fb126
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.AssertionInvocationGroup
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
+import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
+import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
+import android.tools.flicker.config.AssertionTemplates
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerConfigEntry
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.config.ScenarioId
+import android.tools.flicker.config.desktopmode.Components
+import android.tools.flicker.extractors.ITransitionMatcher
+import android.tools.flicker.extractors.ShellTransitionScenarioExtractor
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import android.tools.traces.wm.Transition
+import android.tools.traces.wm.TransitionType
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterDesktopWithDragPortrait : EnterDesktopWithDrag(Rotation.ROTATION_0) {
+ @ExpectedScenarios(["END_DRAG_TO_DESKTOP"]) @Test override fun enterDesktopWithDrag() =
+ super.enterDesktopWithDrag()
+
+ companion object {
+ private val END_DRAG_TO_DESKTOP = FlickerConfigEntry(
+ scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"),
+ extractor = ShellTransitionScenarioExtractor(
+ transitionMatcher = object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions.filter {
+ it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP}
+ }
+ }),
+ assertions = AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
+ AppWindowHasDesktopModeInitialBoundsAtTheEnd(Components.DESKTOP_MODE_APP)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
+
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(END_DRAG_TO_DESKTOP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt
new file mode 100644
index 000000000000..0403b4f64faf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.wm.shell.flicker.service.common.Utils
+import com.android.wm.shell.flicker.utils.DesktopModeUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterDesktopWithDrag
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = SimpleAppHelper(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ }
+
+ @Test
+ open fun enterDesktopWithDrag() {
+ DesktopModeUtils.enterDesktopWithDrag(wmHelper, device, testApp)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/DesktopModeUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/DesktopModeUtils.kt
new file mode 100644
index 000000000000..345bc5ebb20e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/DesktopModeUtils.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.utils
+
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.helpers.SYSTEMUI_PACKAGE
+import android.tools.traces.component.IComponentMatcher
+import android.tools.traces.parsers.WindowManagerStateHelper
+import android.tools.traces.wm.WindowingMode
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+
+/**
+ * Provides a collection of utility functions for desktop mode testing.
+ */
+object DesktopModeUtils {
+ private const val TIMEOUT_MS = 3_000L
+ private const val CAPTION = "desktop_mode_caption"
+ private const val CAPTION_HANDLE = "caption_handle"
+ private const val MAXIMIZE_BUTTON = "maximize_button_view"
+
+ private val captionFullscreen: BySelector
+ get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
+ private val captionHandle: BySelector
+ get() = By.res(SYSTEMUI_PACKAGE, CAPTION_HANDLE)
+ private val maximizeButton: BySelector
+ get() = By.res(SYSTEMUI_PACKAGE, MAXIMIZE_BUTTON)
+
+ /**
+ * Wait for an app moved to desktop to finish its transition.
+ */
+ private fun waitForAppToMoveToDesktop(
+ wmHelper: WindowManagerStateHelper,
+ currentApp: IComponentMatcher,
+ ) {
+ wmHelper
+ .StateSyncBuilder()
+ .withWindowSurfaceAppeared(currentApp)
+ .withFreeformApp(currentApp)
+ .withAppTransitionIdle()
+ .waitForAndVerify()
+ }
+
+ /**
+ * Click maximise button on the app header for the given app.
+ */
+ fun maximiseDesktopApp(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ currentApp: StandardAppHelper
+ ) {
+ if (wmHelper.getWindow(currentApp)?.windowingMode
+ != WindowingMode.WINDOWING_MODE_FREEFORM.value)
+ error("expected a freeform window to maximise but window is not in freefrom mode")
+
+ val maximizeButton =
+ device.wait(Until.findObject(maximizeButton), TIMEOUT_MS)
+ ?: error("Unable to find view $maximizeButton\n")
+ maximizeButton.click()
+ }
+
+ /**
+ * Move an app to Desktop by dragging the app handle at the top.
+ */
+ fun enterDesktopWithDrag(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ currentApp: StandardAppHelper,
+ ) {
+ currentApp.launchViaIntent(wmHelper)
+ dragToDesktop(wmHelper, currentApp, device)
+ waitForAppToMoveToDesktop(wmHelper, currentApp)
+ }
+
+ private fun dragToDesktop(
+ wmHelper: WindowManagerStateHelper,
+ currentApp: StandardAppHelper,
+ device: UiDevice
+ ) {
+ val windowRect = wmHelper.getWindowRegion(currentApp).bounds
+ val startX = windowRect.centerX()
+
+ // Start dragging a little under the top to prevent dragging the notification shade.
+ val startY = 10
+
+ val displayRect =
+ wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+ ?: throw IllegalStateException("Default display is null")
+
+ // The position we want to drag to
+ val endY = displayRect.centerY() / 2
+
+ // drag the window to move to desktop
+ device.drag(startX, startY, startX, endY, 100)
+ }
+}
diff --git a/location/Android.bp b/location/Android.bp
index eb7cd01111b2..5ba35ac3328a 100644
--- a/location/Android.bp
+++ b/location/Android.bp
@@ -26,6 +26,7 @@ java_sdk_library {
"com.android.internal.location",
],
libs: [
+ "android.location.flags-aconfig-java",
"app-compat-annotations",
"unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
],
diff --git a/location/api/current.txt b/location/api/current.txt
index 85e9f65a0718..61afd266aecf 100644
--- a/location/api/current.txt
+++ b/location/api/current.txt
@@ -412,8 +412,8 @@ package android.location {
field public static final int TYPE_GPS_L1CA = 257; // 0x101
field public static final int TYPE_GPS_L2CNAV = 258; // 0x102
field public static final int TYPE_GPS_L5CNAV = 259; // 0x103
- field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L1 = 1795; // 0x703
- field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L5 = 1794; // 0x702
+ field @FlaggedApi("android.location.flags.gnss_api_navic_l1") public static final int TYPE_IRN_L1 = 1795; // 0x703
+ field @FlaggedApi("android.location.flags.gnss_api_navic_l1") public static final int TYPE_IRN_L5 = 1794; // 0x702
field public static final int TYPE_IRN_L5CA = 1793; // 0x701
field public static final int TYPE_QZS_L1CA = 1025; // 0x401
field public static final int TYPE_SBS = 513; // 0x201
@@ -682,7 +682,7 @@ package android.location.altitude {
public final class AltitudeConverter {
ctor public AltitudeConverter();
method @WorkerThread public void addMslAltitudeToLocation(@NonNull android.content.Context, @NonNull android.location.Location) throws java.io.IOException;
- method @FlaggedApi(Flags.FLAG_GEOID_HEIGHTS_VIA_ALTITUDE_HAL) public boolean tryAddMslAltitudeToLocation(@NonNull android.location.Location);
+ method @FlaggedApi("android.location.flags.geoid_heights_via_altitude_hal") public boolean tryAddMslAltitudeToLocation(@NonNull android.location.Location);
}
}
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index 254d74aa235c..f6e76a246947 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -113,13 +113,13 @@ package android.location {
}
public final class GnssMeasurementRequest implements android.os.Parcelable {
- method @FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE) @NonNull public android.os.WorkSource getWorkSource();
+ method @FlaggedApi("android.location.flags.gnss_api_measurement_request_work_source") @NonNull public android.os.WorkSource getWorkSource();
method public boolean isCorrelationVectorOutputsEnabled();
}
public static final class GnssMeasurementRequest.Builder {
method @NonNull public android.location.GnssMeasurementRequest.Builder setCorrelationVectorOutputsEnabled(boolean);
- method @FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE) @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.GnssMeasurementRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
+ method @FlaggedApi("android.location.flags.gnss_api_measurement_request_work_source") @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.GnssMeasurementRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
}
public final class GnssReflectingPlane implements android.os.Parcelable {
@@ -591,7 +591,7 @@ package android.location {
package android.location.provider {
- @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public final class ForwardGeocodeRequest implements android.os.Parcelable {
+ @FlaggedApi("android.location.flags.new_geocoder") public final class ForwardGeocodeRequest implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getCallingAttributionTag();
method @NonNull public String getCallingPackage();
@@ -613,7 +613,7 @@ package android.location.provider {
method @NonNull public android.location.provider.ForwardGeocodeRequest.Builder setCallingAttributionTag(@NonNull String);
}
- @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public abstract class GeocodeProviderBase {
+ @FlaggedApi("android.location.flags.new_geocoder") public abstract class GeocodeProviderBase {
ctor public GeocodeProviderBase(@NonNull android.content.Context, @NonNull String);
method @NonNull public final android.os.IBinder getBinder();
method public abstract void onForwardGeocode(@NonNull android.location.provider.ForwardGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Throwable>);
@@ -672,7 +672,7 @@ package android.location.provider {
method public void onProviderRequestChanged(@NonNull String, @NonNull android.location.provider.ProviderRequest);
}
- @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public final class ReverseGeocodeRequest implements android.os.Parcelable {
+ @FlaggedApi("android.location.flags.new_geocoder") public final class ReverseGeocodeRequest implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getCallingAttributionTag();
method @NonNull public String getCallingPackage();
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
index 105d1a108620..9d477c5b2226 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
index fc845a63efb4..f2e6d2ef3ed3 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
index 19c028e395b7..d31f2c4dcb5c 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt
new file mode 100644
index 000000000000..e883a4a55af9
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+
+/** Sets the content description of this node. */
+fun Modifier.contentDescription(contentDescription: String?) =
+ if (contentDescription != null) this.semantics {
+ this.contentDescription = contentDescription
+ } else this
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 f5cbe8ffcee3..d08d97eb89db 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
@@ -48,10 +48,9 @@ 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.semantics.contentDescription
-import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.debug.UiModePreviews
+import com.android.settingslib.spa.framework.compose.contentDescription
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraLarge
import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraSmall
@@ -191,8 +190,7 @@ private fun Buttons(buttons: List<CardButton>, color: Color) {
private fun Button(button: CardButton, color: Color) {
TextButton(
onClick = button.onClick,
- modifier =
- Modifier.semantics { button.contentDescription?.let { this.contentDescription = it } }
+ modifier = Modifier.contentDescription(button.contentDescription),
) {
Text(text = button.text, color = color)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index 22844362f949..bc5904cc8b9d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -40,6 +40,7 @@ fun SettingsOutlinedTextField(
singleLine: Boolean = true,
enabled: Boolean = true,
shape: Shape = OutlinedTextFieldDefaults.shape,
+ placeholder: @Composable (() -> Unit)? = null,
modifier: Modifier = Modifier
.fillMaxWidth()
.padding(SettingsDimension.textFieldPadding),
@@ -60,6 +61,7 @@ fun SettingsOutlinedTextField(
Text(text = errorMessage)
}
},
+ placeholder = placeholder,
shape = shape
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index 56d75d8bee4b..23a8e78e6c4a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -28,6 +28,7 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -41,7 +42,8 @@ internal fun BaseLayout(
title: String,
subTitle: @Composable () -> Unit,
modifier: Modifier = Modifier,
- icon: (@Composable () -> Unit)? = null,
+ titleContentDescription: String? = null,
+ icon: @Composable (() -> Unit)? = null,
enabled: () -> Boolean = { true },
paddingStart: Dp = SettingsDimension.itemPaddingStart,
paddingEnd: Dp = SettingsDimension.itemPaddingEnd,
@@ -51,6 +53,7 @@ internal fun BaseLayout(
Row(
modifier = modifier
.fillMaxWidth()
+ .semantics(mergeDescendants = true) {}
.padding(end = paddingEnd),
verticalAlignment = Alignment.CenterVertically,
) {
@@ -58,6 +61,7 @@ internal fun BaseLayout(
BaseIcon(icon, alphaModifier, paddingStart)
Titles(
title = title,
+ titleContentDescription = titleContentDescription,
subTitle = subTitle,
modifier = alphaModifier
.weight(1f)
@@ -87,9 +91,14 @@ internal fun BaseIcon(
// Extracts a scope to avoid frequent recompose outside scope.
@Composable
-private fun Titles(title: String, subTitle: @Composable () -> Unit, modifier: Modifier) {
+private fun Titles(
+ title: String,
+ titleContentDescription: String?,
+ subTitle: @Composable () -> Unit,
+ modifier: Modifier,
+) {
Column(modifier) {
- SettingsTitle(title)
+ SettingsTitle(title, titleContentDescription)
subTitle()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
index 194ed81df0ee..e9b3ba2e01f1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
@@ -32,6 +32,8 @@ internal fun BasePreference(
title: String,
summary: () -> String,
modifier: Modifier = Modifier,
+ titleContentDescription: String? = null,
+ summaryContentDescription: () -> String? = { null },
singleLineSummary: Boolean = false,
icon: @Composable (() -> Unit)? = null,
enabled: () -> Boolean = { true },
@@ -42,9 +44,11 @@ internal fun BasePreference(
) {
BaseLayout(
title = title,
+ titleContentDescription = titleContentDescription,
subTitle = {
SettingsBody(
body = summary(),
+ contentDescription = summaryContentDescription(),
maxLines = if (singleLineSummary) 1 else Int.MAX_VALUE,
)
},
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 3acf075d8900..4ad4c1496ce8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -64,12 +64,24 @@ interface PreferenceModel {
val title: String
/**
+ * The content description of [title].
+ */
+ val titleContentDescription: String?
+ get() = null
+
+ /**
* The summary of this [Preference].
*/
val summary: () -> String
get() = { "" }
/**
+ * The content description of [summary].
+ */
+ val summaryContentDescription: () -> String?
+ get() = { null }
+
+ /**
* The icon of this [Preference].
*
* Default is `null` which means no icon.
@@ -112,7 +124,9 @@ fun Preference(
EntryHighlight {
BasePreference(
title = model.title,
+ titleContentDescription = model.titleContentDescription,
summary = model.summary,
+ summaryContentDescription = model.summaryContentDescription,
singleLineSummary = singleLineSummary,
modifier = modifier,
icon = model.icon,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
index 5155406b6d79..2fac576952be 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
@@ -21,8 +21,7 @@ import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.semantics
+import com.android.settingslib.spa.framework.compose.contentDescription
import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog
@Composable
@@ -37,9 +36,7 @@ internal fun SettingsSwitch(
Switch(
checked = checked,
onCheckedChange = wrapOnSwitchWithLog(onCheckedChange),
- modifier = if (contentDescription != null) Modifier.semantics {
- this.contentDescription = contentDescription
- } else Modifier,
+ modifier = Modifier.contentDescription(contentDescription),
enabled = changeable(),
interactionSource = interactionSource,
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index a59b95a60879..d423d9fe5897 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -30,16 +30,23 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.compose.contentDescription
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.theme.toMediumWeight
@Composable
-fun SettingsTitle(title: String, useMediumWeight: Boolean = false) {
+fun SettingsTitle(
+ title: String,
+ contentDescription: String? = null,
+ useMediumWeight: Boolean = false,
+) {
Text(
text = title,
- modifier = Modifier.padding(vertical = SettingsDimension.paddingTiny),
+ modifier = Modifier
+ .padding(vertical = SettingsDimension.paddingTiny)
+ .contentDescription(contentDescription),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleMedium.withWeight(useMediumWeight),
)
@@ -81,11 +88,13 @@ fun SettingsListItem(text: String, enabled: Boolean = true) {
@Composable
fun SettingsBody(
body: String,
+ contentDescription: String? = null,
maxLines: Int = Int.MAX_VALUE,
) {
if (body.isNotEmpty()) {
Text(
text = body,
+ modifier = Modifier.contentDescription(contentDescription),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
overflow = TextOverflow.Ellipsis,
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
index 8c363db92e19..5ef3329541f2 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
@@ -29,6 +29,7 @@ import androidx.compose.ui.test.assertHeightIsAtLeast
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -93,11 +94,10 @@ class PreferenceTest {
}
}
- val summaryNode = composeTestRule.onNodeWithText(LONG_SUMMARY)
try {
// There is no assertHeightIsAtMost, so use the assertHeightIsAtLeast and catch the
// expected exception.
- summaryNode.assertHeightIsAtLeast(lineHeightDp.times(2))
+ composeTestRule.onRoot().assertHeightIsAtLeast(lineHeightDp.times(5))
} catch (e: AssertionError) {
assertThat(e).hasMessageThat().contains("height")
return
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
index 5ed59996bee3..2da622139b79 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
@@ -16,6 +16,8 @@
package com.android.settingslib.utils;
+import static java.lang.Math.abs;
+
import android.content.Context;
import android.icu.text.DateFormat;
import android.icu.text.MeasureFormat;
@@ -212,8 +214,8 @@ public class PowerUtil {
* @return The rounded value as a long
*/
public static long roundTimeToNearestThreshold(long drainTime, long threshold) {
- long time = Math.abs(drainTime);
- long multiple = Math.abs(threshold);
+ long time = abs(drainTime);
+ long multiple = abs(threshold);
final long remainder = time % multiple;
if (remainder < multiple / 2) {
return time - remainder;
@@ -222,18 +224,24 @@ public class PowerUtil {
}
}
- /** Gets the rounded target time string in a short format. */
+ /** Gets the target time string in a short format. */
public static String getTargetTimeShortString(
Context context, long targetTimeOffsetMs, long currentTimeMs) {
- final long roundedTimeOfDayMs =
- roundTimeToNearestThreshold(
- currentTimeMs + targetTimeOffsetMs, FIFTEEN_MINUTES_MILLIS);
+ long targetTimeMs = currentTimeMs + targetTimeOffsetMs;
+ if (targetTimeOffsetMs >= FIFTEEN_MINUTES_MILLIS) {
+ targetTimeMs = roundUpTimeToNextThreshold(targetTimeMs, FIFTEEN_MINUTES_MILLIS);
+ }
// convert the time to a properly formatted string.
String skeleton = android.text.format.DateFormat.getTimeFormatString(context);
DateFormat fmt = DateFormat.getInstanceForSkeleton(skeleton);
- Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs));
+ Date date = Date.from(Instant.ofEpochMilli(targetTimeMs));
return fmt.format(date);
}
-}
+ private static long roundUpTimeToNextThreshold(long timeMs, long threshold) {
+ var time = abs(timeMs);
+ var multiple = abs(threshold);
+ return ((time + multiple - 1) / multiple) * multiple;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
index cbc382b6b920..4f3b2005b197 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
@@ -87,15 +87,31 @@ public class PowerUtilTest {
}
@Test
- public void getTargetTimeShortString_returnsTimeShortString() {
+ public void getTargetTimeShortString_lessThan15Minutes_returnsTimeShortStringWithoutRounded() {
mContext.getSystemService(AlarmManager.class).setTimeZone("UTC");
mContext.getResources().getConfiguration().setLocale(Locale.US);
var currentTimeMs = Instant.parse("2024-06-06T15:00:00Z").toEpochMilli();
- var remainingTimeMs = Duration.ofMinutes(30).toMillis();
+ var remainingTimeMs = Duration.ofMinutes(15).toMillis() - 1;
var actualTimeString =
PowerUtil.getTargetTimeShortString(mContext, remainingTimeMs, currentTimeMs);
- assertThat(actualTimeString).isEqualTo("3:30 PM");
+ // due to timezone issue in test case, focus on rounded minutes, remove hours part.
+ assertThat(actualTimeString).endsWith("14 PM");
+ }
+
+ @Test
+ public void getTargetTimeShortString_moreThan15Minutes_returnsTimeShortStringWithRounded() {
+ mContext.getSystemService(AlarmManager.class).setTimeZone("UTC");
+ mContext.getResources().getConfiguration().setLocale(Locale.US);
+ var currentTimeMs = Instant.parse("2024-06-06T15:00:00Z").toEpochMilli();
+ var remainingTimeMs = Duration.ofMinutes(15).toMillis() + 1;
+
+ var actualTimeString =
+ PowerUtil.getTargetTimeShortString(mContext, remainingTimeMs, currentTimeMs);
+
+ // due to timezone issue in test case, focus on rounded minutes, remove hours part.
+ assertThat(actualTimeString).endsWith("30 PM");
+
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
index df27cbb070b6..027f6744d4d7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
@@ -35,6 +35,8 @@ public class BiometricNotificationBroadcastReceiver extends BroadcastReceiver {
static final String ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG =
"fingerprint_action_show_reenroll_dialog";
+ static final String EXTRA_IS_REENROLL_FORCED = "is_reenroll_forced";
+
private static final String TAG = "BiometricNotificationBroadcastReceiver";
private final Context mContext;
@@ -56,14 +58,16 @@ public class BiometricNotificationBroadcastReceiver extends BroadcastReceiver {
mNotificationDialogFactory.createReenrollDialog(
mContext.getUserId(),
mContext::startActivity,
- BiometricSourceType.FACE)
+ BiometricSourceType.FACE,
+ false)
.show();
break;
case ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG:
mNotificationDialogFactory.createReenrollDialog(
mContext.getUserId(),
mContext::startActivity,
- BiometricSourceType.FINGERPRINT)
+ BiometricSourceType.FINGERPRINT,
+ intent.getBooleanExtra(EXTRA_IS_REENROLL_FORCED, false))
.show();
break;
default:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java
index fd0feef7689b..4ac5a12dae03 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java
@@ -29,13 +29,12 @@ import android.hardware.fingerprint.FingerprintManager;
import android.provider.Settings;
import android.util.Log;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import javax.inject.Inject;
-import javax.inject.Provider;
/**
* Manages the creation of dialogs to be shown for biometric re enroll notifications.
@@ -61,7 +60,8 @@ public class BiometricNotificationDialogFactory {
}
Dialog createReenrollDialog(
- int userId, ActivityStarter activityStarter, BiometricSourceType biometricSourceType) {
+ int userId, ActivityStarter activityStarter, BiometricSourceType biometricSourceType,
+ boolean isReenrollForced) {
SystemUIDialog sysuiDialog = mSystemUIDialogFactory.create();
if (biometricSourceType == BiometricSourceType.FACE) {
sysuiDialog.setTitle(mResources.getString(R.string.face_re_enroll_dialog_title));
@@ -80,8 +80,12 @@ public class BiometricNotificationDialogFactory {
sysuiDialog.setPositiveButton(R.string.biometric_re_enroll_dialog_confirm,
(dialog, which) -> onReenrollDialogConfirm(
userId, biometricSourceType, activityStarter));
- sysuiDialog.setNegativeButton(R.string.biometric_re_enroll_dialog_cancel,
- (dialog, which) -> {});
+ if (!isReenrollForced) {
+ sysuiDialog.setNegativeButton(R.string.biometric_re_enroll_dialog_cancel,
+ (dialog, which) -> {
+ });
+ }
+ sysuiDialog.setCanceledOnTouchOutside(!isReenrollForced);
return sysuiDialog;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
index d6a4cbb67487..3b49ce2f10f7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG;
import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG;
@@ -43,8 +44,8 @@ import android.util.Log;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.util.Optional;
@@ -80,6 +81,8 @@ public class BiometricNotificationService implements CoreStartable {
private boolean mFingerprintNotificationQueued;
private boolean mFingerprintReenrollRequired;
+ private boolean mIsFingerprintReenrollForced;
+
private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
new KeyguardStateController.Callback() {
private boolean mIsShowing = true;
@@ -118,9 +121,11 @@ public class BiometricNotificationService implements CoreStartable {
public void onBiometricHelp(int msgId, String helpString,
BiometricSourceType biometricSourceType) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT
- && mFingerprintReEnrollNotification.isFingerprintReEnrollRequired(
+ && mFingerprintReEnrollNotification.isFingerprintReEnrollRequested(
msgId)) {
mFingerprintReenrollRequired = true;
+ mIsFingerprintReenrollForced =
+ mFingerprintReEnrollNotification.isFingerprintReEnrollForced(msgId);
}
}
};
@@ -191,7 +196,7 @@ public class BiometricNotificationService implements CoreStartable {
final String name = mContext.getString(R.string.face_re_enroll_notification_name);
mHandler.postDelayed(
() -> showNotification(ACTION_SHOW_FACE_REENROLL_DIALOG, title, content, name,
- FACE_NOTIFICATION_ID),
+ FACE_NOTIFICATION_ID, false),
SHOW_NOTIFICATION_DELAY_MS);
}
@@ -204,12 +209,12 @@ public class BiometricNotificationService implements CoreStartable {
final String name = mContext.getString(R.string.fingerprint_re_enroll_notification_name);
mHandler.postDelayed(
() -> showNotification(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG, title, content,
- name, FINGERPRINT_NOTIFICATION_ID),
+ name, FINGERPRINT_NOTIFICATION_ID, mIsFingerprintReenrollForced),
SHOW_NOTIFICATION_DELAY_MS);
}
private void showNotification(String action, CharSequence title, CharSequence content,
- CharSequence name, int notificationId) {
+ CharSequence name, int notificationId, boolean isReenrollForced) {
if (notificationId == FACE_NOTIFICATION_ID) {
mFaceNotificationQueued = false;
} else if (notificationId == FINGERPRINT_NOTIFICATION_ID) {
@@ -223,8 +228,12 @@ public class BiometricNotificationService implements CoreStartable {
}
final Intent onClickIntent = new Intent(action);
+ onClickIntent.putExtra(BiometricNotificationBroadcastReceiver.EXTRA_IS_REENROLL_FORCED,
+ isReenrollForced);
+
final PendingIntent onClickPendingIntent = PendingIntent.getBroadcastAsUser(mContext,
- 0 /* requestCode */, onClickIntent, FLAG_IMMUTABLE, UserHandle.CURRENT);
+ 0 /* requestCode */, onClickIntent, FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT,
+ UserHandle.CURRENT);
final Notification notification = new Notification.Builder(mContext, CHANNEL_ID)
.setCategory(Notification.CATEGORY_SYSTEM)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
index 9050f26d39e4..5b9ed483da02 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
@@ -23,7 +23,16 @@ import android.hardware.biometrics.BiometricFingerprintConstants;
*/
public interface FingerprintReEnrollNotification {
//TODO: Remove this class and add a constant in the HAL API instead (b/281841852)
- /** Returns true if msgId corresponds to FINGERPRINT_ACQUIRED_RE_ENROLL. */
- boolean isFingerprintReEnrollRequired(
+ /**
+ * Returns true if msgId corresponds to FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL or
+ * FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED.
+ */
+ boolean isFingerprintReEnrollRequested(
+ @BiometricFingerprintConstants.FingerprintAcquired int msgId);
+
+ /**
+ * Returns true if msgId corresponds to FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED.
+ */
+ boolean isFingerprintReEnrollForced(
@BiometricFingerprintConstants.FingerprintAcquired int msgId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
index 1f86bc6ae298..d47e1e67796e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
@@ -23,7 +23,13 @@ import android.hardware.biometrics.BiometricFingerprintConstants;
*/
public class FingerprintReEnrollNotificationImpl implements FingerprintReEnrollNotification{
@Override
- public boolean isFingerprintReEnrollRequired(int msgId) {
- return msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL;
+ public boolean isFingerprintReEnrollRequested(int msgId) {
+ return msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL
+ || msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED;
+ }
+
+ @Override
+ public boolean isFingerprintReEnrollForced(int msgId) {
+ return msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 4ee2db796aef..042fb63f949c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -75,6 +75,10 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ // set layer to make alpha animation of brightness slider nicer - otherwise elements
+ // of slider are animated separately and it doesn't look good. See b/329244723
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+
mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view);
mQSPanel = findViewById(R.id.quick_settings_panel);
mHeader = findViewById(R.id.header);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index 92006a473ed8..e051dab3a2b5 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -63,7 +63,6 @@ public class BrightnessSliderView extends FrameLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- setLayerType(LAYER_TYPE_HARDWARE, null);
mSlider = requireViewById(R.id.slider);
mSlider.setAccessibilityLabel(getContentDescription().toString());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 4ebb6998b8c7..271b0a86ca12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -39,13 +39,13 @@ import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import java.util.concurrent.ConcurrentHashMap
-import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
/**
* Inflates and updates icons associated with notifications
@@ -239,8 +239,8 @@ constructor(
val sbi = icon.toStatusBarIcon(entry)
- // Cache if important conversation.
- if (isImportantConversation(entry)) {
+ // Cache if important conversation or app icon.
+ if (isImportantConversation(entry) || android.app.Flags.notificationsUseAppIcon()) {
if (showPeopleAvatar) {
entry.icons.peopleAvatarDescriptor = sbi
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
index bfeaced72162..2fdd2c6434cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
@@ -20,6 +20,7 @@ import android.graphics.Rect
import android.view.View
import com.android.app.tracing.traceSection
import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.Flags
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.NO_COLOR
@@ -34,9 +35,12 @@ object StatusBarIconViewBinder {
// view-model (which, at the time of this writing, does not yet exist).
suspend fun bindColor(view: StatusBarIconView, color: Flow<Int>) {
- color.collectTracingEach("SBIV#bindColor") { color ->
- view.staticDrawableColor = color
- view.setDecorColor(color)
+ // Don't change the icon color if an app icon experiment is enabled.
+ if (!android.app.Flags.notificationsUseAppIcon()) {
+ color.collectTracingEach("SBIV#bindColor") { color ->
+ view.staticDrawableColor = color
+ view.setDecorColor(color)
+ }
}
}
@@ -53,12 +57,15 @@ object StatusBarIconViewBinder {
iconColors: Flow<NotificationIconColors>,
contrastColorUtil: ContrastColorUtil,
) {
- iconColors.collectTracingEach("SBIV#bindIconColors") { colors ->
- val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L)
- val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil)
- view.staticDrawableColor =
- if (isColorized) colors.staticDrawableColor(view.viewBounds) else NO_COLOR
- view.setDecorColor(colors.tint)
+ // Don't change the icon color if an app icon experiment is enabled.
+ if (!android.app.Flags.notificationsUseAppIcon()) {
+ iconColors.collectTracingEach("SBIV#bindIconColors") { colors ->
+ val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L)
+ val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil)
+ view.staticDrawableColor =
+ if (isColorized) colors.staticDrawableColor(view.viewBounds) else NO_COLOR
+ view.setDecorColor(colors.tint)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index 6acb12a3e2af..52cb48be041f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -16,13 +16,12 @@
package com.android.systemui.statusbar.notification.ui.viewbinder
-import android.util.Log
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.util.kotlin.sample
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.coroutineScope
@@ -77,8 +76,7 @@ constructor(private val viewModel: NotificationListViewModel) {
}
private val NotificationStackScrollLayout.isHeadsUpAnimatingAway: Flow<Boolean>
- get() =
- ConflatedCallbackFlow.conflatedCallbackFlow {
- setHeadsUpAnimatingAwayListener { animatingAway -> trySend(animatingAway) }
- awaitClose { setHeadsUpAnimatingAwayListener(null) }
- }
+ get() = conflatedCallbackFlow {
+ setHeadsUpAnimatingAwayListener { animatingAway -> trySend(animatingAway) }
+ awaitClose { setHeadsUpAnimatingAwayListener(null) }
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
index 8c8544cd6e5b..d2c695739ea9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics;
import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -90,7 +91,8 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase {
assumeTrue(getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
- mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT);
+ mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT,
+ false);
verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
@@ -115,7 +117,8 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase {
assumeTrue(getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
- mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT);
+ mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT,
+ false);
verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
@@ -134,11 +137,25 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase {
}
@Test
+ public void testFingerprintReEnrollDialog_forced() {
+ assumeTrue(getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
+
+ mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT,
+ true);
+
+ verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
+
+ verify(mDialog, never()).setNegativeButton(anyInt(), any());
+ }
+
+ @Test
public void testFaceReEnrollDialog_onRemovalSucceeded() {
assumeTrue(getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_FACE));
- mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE);
+ mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE,
+ false);
verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
@@ -163,7 +180,8 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase {
assumeTrue(getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_FACE));
- mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE);
+ mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE,
+ false);
verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
index c6771b262114..a279d3ee67e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
@@ -18,7 +18,9 @@ package com.android.systemui.biometrics;
import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG;
import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -98,7 +100,7 @@ public class BiometricNotificationServiceTest extends SysuiTestCase {
public void setUp() {
when(mFingerprintReEnrollNotificationOptional.orElse(any()))
.thenReturn(mFingerprintReEnrollNotification);
- when(mFingerprintReEnrollNotification.isFingerprintReEnrollRequired(
+ when(mFingerprintReEnrollNotification.isFingerprintReEnrollRequested(
FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(true);
mLooper = TestableLooper.get(this);
@@ -140,8 +142,37 @@ public class BiometricNotificationServiceTest extends SysuiTestCase {
}
@Test
- public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll() {
+ public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll_Optional() {
when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mFingerprintReEnrollNotification.isFingerprintReEnrollForced(
+ FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(false);
+
+ mKeyguardUpdateMonitorCallback.onBiometricHelp(
+ FINGERPRINT_ACQUIRED_RE_ENROLL,
+ "Testing Fingerprint Re-enrollment" /* errString */,
+ BiometricSourceType.FINGERPRINT
+ );
+ mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+
+ mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
+ mLooper.processAllMessages();
+
+ verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID),
+ mNotificationArgumentCaptor.capture(), any());
+
+ Notification fingerprintNotification = mNotificationArgumentCaptor.getValue();
+
+ assertThat(fingerprintNotification.contentIntent.getIntent().getAction())
+ .isEqualTo(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG);
+ assertThat(fingerprintNotification.contentIntent.getIntent().getBooleanExtra(
+ BiometricNotificationBroadcastReceiver.EXTRA_IS_REENROLL_FORCED, false)).isFalse();
+ }
+
+ @Test
+ public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll_force() {
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mFingerprintReEnrollNotification.isFingerprintReEnrollForced(
+ FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(true);
mKeyguardUpdateMonitorCallback.onBiometricHelp(
FINGERPRINT_ACQUIRED_RE_ENROLL,
@@ -160,6 +191,8 @@ public class BiometricNotificationServiceTest extends SysuiTestCase {
assertThat(fingerprintNotification.contentIntent.getIntent().getAction())
.isEqualTo(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG);
+ assertThat(fingerprintNotification.contentIntent.getIntent().getBooleanExtra(
+ BiometricNotificationBroadcastReceiver.EXTRA_IS_REENROLL_FORCED, false)).isTrue();
}
@Test
public void testShowFaceReEnrollNotification_onErrorReEnroll() {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 1bd93e4dbc36..b8bfedae91ae 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -4727,9 +4727,14 @@ public class AppOpsService extends IAppOpsService.Stub {
}
if ((code == OP_CAMERA) && isAutomotive()) {
- if ((Flags.cameraPrivacyAllowlist())
- && (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName))) {
- return true;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if ((Flags.cameraPrivacyAllowlist())
+ && (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName))) {
+ return true;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 32a0ef4c6a42..8002300dacc0 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -18,6 +18,7 @@ package com.android.server.location.gnss;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.location.flags.Flags;
import android.net.ConnectivityManager;
@@ -32,6 +33,7 @@ import android.os.Looper;
import android.os.PowerManager;
import android.telephony.PhoneStateListener;
import android.telephony.PreciseCallState;
+import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -572,6 +574,10 @@ class GnssNetworkConnectivityHandler {
}
}
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ android.Manifest.permission.READ_PHONE_STATE
+ })
private void handleRequestSuplConnection(int agpsType, byte[] suplIpAddr) {
mAGpsDataConnectionIpAddr = null;
mAGpsType = agpsType;
@@ -605,6 +611,19 @@ class GnssNetworkConnectivityHandler {
NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
networkRequestBuilder.addCapability(getNetworkCapability(mAGpsType));
networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+
+ if (com.android.internal.telephony.flags.Flags.satelliteInternet()) {
+ // Add transport type NetworkCapabilities.TRANSPORT_SATELLITE on satellite network.
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ if (telephonyManager != null) {
+ ServiceState state = telephonyManager.getServiceState();
+ if (state != null && state.isUsingNonTerrestrialNetwork()) {
+ networkRequestBuilder.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+ }
+ }
+ }
+
// During an emergency call, and when we have cached the Active Sub Id, we set the
// Network Specifier so that the network request goes to the correct Sub Id
if (mNiHandler.getInEmergency() && mActiveSubId >= 0) {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 3d6855547bcd..a3c5d2d336f2 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -66,7 +66,6 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
-import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -90,7 +89,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
@@ -191,6 +189,9 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
private final Object mLock = new Object();
+ // This field is partially guarded by mLock. Writes and non-atomic iterations (for example:
+ // index-based-iterations) must be guarded by mLock. But it is safe to acquire an iterator
+ // without acquiring mLock.
private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
mControllerCallbackHolders = new CopyOnWriteArrayList<>();
@@ -886,24 +887,9 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
playbackState = mPlaybackState;
}
- Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
- for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
- try {
- holder.mCallback.onPlaybackStateChanged(playbackState);
- } catch (DeadObjectException e) {
- if (deadCallbackHolders == null) {
- deadCallbackHolders = new ArrayList<>();
- }
- deadCallbackHolders.add(holder);
- logCallbackException("Removing dead callback in pushPlaybackStateUpdate", holder,
- e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushPlaybackStateUpdate", holder, e);
- }
- }
- if (deadCallbackHolders != null) {
- removeControllerHoldersSafely(deadCallbackHolders);
- }
+ performOnCallbackHolders(
+ "pushPlaybackStateUpdate",
+ holder -> holder.mCallback.onPlaybackStateChanged(playbackState));
}
private void pushMetadataUpdate() {
@@ -914,23 +900,8 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
metadata = mMetadata;
}
- Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
- for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
- try {
- holder.mCallback.onMetadataChanged(metadata);
- } catch (DeadObjectException e) {
- if (deadCallbackHolders == null) {
- deadCallbackHolders = new ArrayList<>();
- }
- deadCallbackHolders.add(holder);
- logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushMetadataUpdate", holder, e);
- }
- }
- if (deadCallbackHolders != null) {
- removeControllerHoldersSafely(deadCallbackHolders);
- }
+ performOnCallbackHolders(
+ "pushMetadataUpdate", holder -> holder.mCallback.onMetadataChanged(metadata));
}
private void pushQueueUpdate() {
@@ -941,31 +912,18 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
toSend = mQueue == null ? null : new ArrayList<>(mQueue);
}
- Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
- for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
- ParceledListSlice<QueueItem> parcelableQueue = null;
- if (toSend != null) {
- parcelableQueue = new ParceledListSlice<>(toSend);
- // Limit the size of initial Parcel to prevent binder buffer overflow
- // as onQueueChanged is an async binder call.
- parcelableQueue.setInlineCountLimit(1);
- }
-
- try {
- holder.mCallback.onQueueChanged(parcelableQueue);
- } catch (DeadObjectException e) {
- if (deadCallbackHolders == null) {
- deadCallbackHolders = new ArrayList<>();
- }
- deadCallbackHolders.add(holder);
- logCallbackException("Removing dead callback in pushQueueUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
- }
- }
- if (deadCallbackHolders != null) {
- removeControllerHoldersSafely(deadCallbackHolders);
- }
+ performOnCallbackHolders(
+ "pushQueueUpdate",
+ holder -> {
+ ParceledListSlice<QueueItem> parcelableQueue = null;
+ if (toSend != null) {
+ parcelableQueue = new ParceledListSlice<>(toSend);
+ // Limit the size of initial Parcel to prevent binder buffer overflow
+ // as onQueueChanged is an async binder call.
+ parcelableQueue.setInlineCountLimit(1);
+ }
+ holder.mCallback.onQueueChanged(parcelableQueue);
+ });
}
private void pushQueueTitleUpdate() {
@@ -976,23 +934,8 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
queueTitle = mQueueTitle;
}
- Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
- for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
- try {
- holder.mCallback.onQueueTitleChanged(queueTitle);
- } catch (DeadObjectException e) {
- if (deadCallbackHolders == null) {
- deadCallbackHolders = new ArrayList<>();
- }
- deadCallbackHolders.add(holder);
- logCallbackException("Removing dead callback in pushQueueTitleUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushQueueTitleUpdate", holder, e);
- }
- }
- if (deadCallbackHolders != null) {
- removeControllerHoldersSafely(deadCallbackHolders);
- }
+ performOnCallbackHolders(
+ "pushQueueTitleUpdate", holder -> holder.mCallback.onQueueTitleChanged(queueTitle));
}
private void pushExtrasUpdate() {
@@ -1003,23 +946,8 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
extras = mExtras;
}
- Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
- for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
- try {
- holder.mCallback.onExtrasChanged(extras);
- } catch (DeadObjectException e) {
- if (deadCallbackHolders == null) {
- deadCallbackHolders = new ArrayList<>();
- }
- deadCallbackHolders.add(holder);
- logCallbackException("Removing dead callback in pushExtrasUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
- }
- }
- if (deadCallbackHolders != null) {
- removeControllerHoldersSafely(deadCallbackHolders);
- }
+ performOnCallbackHolders(
+ "pushExtrasUpdate", holder -> holder.mCallback.onExtrasChanged(extras));
}
private void pushVolumeUpdate() {
@@ -1030,23 +958,8 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
info = getVolumeAttributes();
}
- Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
- for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
- try {
- holder.mCallback.onVolumeInfoChanged(info);
- } catch (DeadObjectException e) {
- if (deadCallbackHolders == null) {
- deadCallbackHolders = new ArrayList<>();
- }
- deadCallbackHolders.add(holder);
- logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushVolumeUpdate", holder, e);
- }
- }
- if (deadCallbackHolders != null) {
- removeControllerHoldersSafely(deadCallbackHolders);
- }
+ performOnCallbackHolders(
+ "pushVolumeUpdate", holder -> holder.mCallback.onVolumeInfoChanged(info));
}
private void pushEvent(String event, Bundle data) {
@@ -1055,23 +968,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
return;
}
}
- Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
- for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
- try {
- holder.mCallback.onEvent(event, data);
- } catch (DeadObjectException e) {
- if (deadCallbackHolders == null) {
- deadCallbackHolders = new ArrayList<>();
- }
- deadCallbackHolders.add(holder);
- logCallbackException("Removing dead callback in pushEvent", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushEvent", holder, e);
- }
- }
- if (deadCallbackHolders != null) {
- removeControllerHoldersSafely(deadCallbackHolders);
- }
+ performOnCallbackHolders("pushEvent", holder -> holder.mCallback.onEvent(event, data));
}
private void pushSessionDestroyed() {
@@ -1082,20 +979,37 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
return;
}
}
+ performOnCallbackHolders(
+ "pushSessionDestroyed",
+ holder -> {
+ holder.mCallback.asBinder().unlinkToDeath(holder.mDeathMonitor, 0);
+ holder.mCallback.onSessionDestroyed();
+ });
+ // After notifying clear all listeners
+ synchronized (mLock) {
+ mControllerCallbackHolders.clear();
+ }
+ }
+
+ private interface ControllerCallbackCall {
+
+ void performOn(ISessionControllerCallbackHolder holder) throws RemoteException;
+ }
+
+ private void performOnCallbackHolders(String operationName, ControllerCallbackCall call) {
+ ArrayList<ISessionControllerCallbackHolder> deadCallbackHolders = new ArrayList<>();
for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
try {
- holder.mCallback.asBinder().unlinkToDeath(holder.mDeathMonitor, 0);
- holder.mCallback.onSessionDestroyed();
- } catch (NoSuchElementException e) {
- logCallbackException("error unlinking to binder death", holder, e);
- } catch (DeadObjectException e) {
- logCallbackException("Removing dead callback in pushSessionDestroyed", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushSessionDestroyed", holder, e);
+ call.performOn(holder);
+ } catch (RemoteException | NoSuchElementException exception) {
+ deadCallbackHolders.add(holder);
+ logCallbackException(
+ "Exception while executing: " + operationName, holder, exception);
}
}
- // After notifying clear all listeners
- removeControllerHoldersSafely(null);
+ synchronized (mLock) {
+ mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ }
}
private PlaybackState getStateWithUpdatedPosition() {
@@ -1143,17 +1057,6 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
return -1;
}
- private void removeControllerHoldersSafely(
- Collection<ISessionControllerCallbackHolder> holders) {
- synchronized (mLock) {
- if (holders == null) {
- mControllerCallbackHolders.clear();
- } else {
- mControllerCallbackHolders.removeAll(holders);
- }
- }
- }
-
private PlaybackInfo getVolumeAttributes() {
int volumeType;
AudioAttributes attributes;
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 20b7fd46beb5..4a3812bf0f49 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1493,9 +1493,13 @@ public class ZenModeHelper {
newConfig = mConfig.copy();
if (zenMode == Global.ZEN_MODE_OFF) {
newConfig.manualRule = null;
- for (ZenRule automaticRule : newConfig.automaticRules.values()) {
- if (automaticRule.isAutomaticActive()) {
- automaticRule.snoozing = true;
+ if (!Flags.modesUi() || origin != UPDATE_ORIGIN_USER) {
+ // User deactivation of DND means just turning off the manual DND rule.
+ // For API calls (different origin) keep old behavior of snoozing all rules.
+ for (ZenRule automaticRule : newConfig.automaticRules.values()) {
+ if (automaticRule.isAutomaticActive()) {
+ automaticRule.snoozing = true;
+ }
}
}
} else {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 91860a6db942..390a7cf2d09e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4805,6 +4805,14 @@ class Task extends TaskFragment {
topActivity.getSyncTransaction());
}
lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask");
+ // If the reparent is not included in transition, make sure the visibility of
+ // task is still updated by core. Otherwise if the task is collected (e.g.
+ // rotation change) after leaving this scope, the visibility operation will be
+ // put in sync transaction, then it is not synced with reparent.
+ if (com.android.window.flags.Flags.removePrepareSurfaceInPlacement()
+ && lastParentBeforePip.mSyncState == SYNC_STATE_NONE) {
+ lastParentBeforePip.prepareSurfaces();
+ }
}
if (isPip2ExperimentEnabled) {
super.setWindowingMode(windowingMode);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7e083ba8859a..be235b3bef92 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -821,6 +821,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public static final long THROW_SECURITY_EXCEPTION_FOR_SENSOR_PERMISSIONS = 277035314L;
+ /**
+ * Allows DPCs to provisioning fully managed headless devices in single-user mode.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = 35)
+ public static final long PROVISION_SINGLE_USER_MODE = 289515470L;
+
// Only add to the end of the list. Do not change or rearrange these values, that will break
// historical data. Do not use negative numbers or zero, logger only handles positive
// integers.
@@ -6834,7 +6841,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// If there is a profile owner, redirect to that; otherwise query the device owner.
ComponentName aliasChooser = getProfileOwnerAsUser(caller.getUserId());
- if (aliasChooser == null && caller.getUserHandle().isSystem()) {
+ boolean isDoUser = Flags.headlessSingleUserFixes()
+ ? caller.getUserId() == getDeviceOwnerUserId()
+ : caller.getUserHandle().isSystem();
+ if (aliasChooser == null && isDoUser) {
synchronized (getLockObject()) {
final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
if (deviceOwnerAdmin != null) {
@@ -7828,7 +7838,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mInjector.binderWithCleanCallingIdentity(() -> {
// First check whether the admin is allowed to wipe the device/user/profile.
final String restriction;
- if (userId == UserHandle.USER_SYSTEM) {
+ boolean shouldFactoryReset = userId == UserHandle.USER_SYSTEM;
+ if (Flags.headlessSingleUserFixes() && getHeadlessDeviceOwnerModeForDeviceOwner()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER) {
+ shouldFactoryReset = userId == getMainUserId();
+ }
+ if (shouldFactoryReset) {
restriction = UserManager.DISALLOW_FACTORY_RESET;
} else if (isManagedProfile(userId)) {
restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
@@ -7842,12 +7857,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
});
boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+ boolean isMainUser = userId == getMainUserId();
boolean wipeDevice;
if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
adminPackage,
userId)) {
// Legacy mode
- wipeDevice = isSystemUser;
+ wipeDevice = Flags.headlessSingleUserFixes()
+ && getHeadlessDeviceOwnerModeForDeviceOwner()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser;
} else {
// Explicit behaviour
if (factoryReset) {
@@ -8185,6 +8203,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
userHandle, /* parent= */ false);
int max = strictestAdmin != null
? strictestAdmin.maximumFailedPasswordsForWipe : 0;
+
if (max > 0 && policy.mFailedPasswordAttempts >= max) {
wipeData = true;
}
@@ -18398,6 +18417,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| isProfileOwner(caller) || isFinancedDeviceOwner(caller));
+ // Backup service has to be enabled on the main user in order for it to be enabled on
+ // secondary users.
+ if (Flags.headlessSingleUserFixes() && isDeviceOwner(caller)
+ && getHeadlessDeviceOwnerModeForDeviceOwner()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER) {
+ toggleBackupServiceActive(UserHandle.USER_SYSTEM, enabled);
+ }
+
toggleBackupServiceActive(caller.getUserId(), enabled);
if (Flags.backupServiceSecurityLogEventEnabled()) {
@@ -21745,7 +21772,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Objects.requireNonNull(deviceAdmin, "admin is null.");
Objects.requireNonNull(provisioningParams.getOwnerName(), "owner name is null.");
- final CallerIdentity caller = getCallerIdentity();
+ final CallerIdentity caller = getCallerIdentity(callerPackage);
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
|| (hasCallingOrSelfPermission(permission.PROVISION_DEMO_DEVICE)
@@ -21755,6 +21782,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final long identity = Binder.clearCallingIdentity();
try {
+ boolean isSingleUserMode;
+ if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+ int headlessDeviceOwnerMode = getHeadlessDeviceOwnerModeForDeviceAdmin(
+ deviceAdmin, caller.getUserId());
+ isSingleUserMode =
+ headlessDeviceOwnerMode == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
+ } else {
+ isSingleUserMode =
+ getHeadlessDeviceOwnerModeForDeviceOwner()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
+ }
+
+ if (Flags.headlessSingleUserFixes() && isSingleUserMode && !mInjector.isChangeEnabled(
+ PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(), caller.getUserId())) {
+ throw new IllegalStateException("Device admin is not targeting Android V.");
+ }
+
int result = checkProvisioningPreconditionSkipPermission(
ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin, caller.getUserId());
if (result != STATUS_OK) {
@@ -21768,17 +21812,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
- boolean isSingleUserMode;
- if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
- int headlessDeviceOwnerMode = getHeadlessDeviceOwnerModeForDeviceAdmin(
- deviceAdmin, caller.getUserId());
- isSingleUserMode =
- headlessDeviceOwnerMode == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
- } else {
- isSingleUserMode =
- getHeadlessDeviceOwnerModeForDeviceOwner()
- == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
- }
int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
&& isSingleUserMode
? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 43f24750ddef..5adfafb8a57c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -322,6 +322,27 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeEventLogger.reset();
}
+ private enum ChangeOrigin {
+ ORIGIN_UNKNOWN(ZenModeConfig.UPDATE_ORIGIN_UNKNOWN),
+ ORIGIN_INIT(ZenModeConfig.UPDATE_ORIGIN_INIT),
+ ORIGIN_INIT_USER(ZenModeConfig.UPDATE_ORIGIN_INIT_USER),
+ ORIGIN_USER(ZenModeConfig.UPDATE_ORIGIN_USER),
+ ORIGIN_APP(ZenModeConfig.UPDATE_ORIGIN_APP),
+ ORIGIN_SYSTEM_OR_SYSTEMUI(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI),
+ ORIGIN_RESTORE_BACKUP(ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP);
+
+ private final int mValue;
+
+ ChangeOrigin(@ZenModeConfig.ConfigChangeOrigin int value) {
+ mValue = value;
+ }
+
+ @ZenModeConfig.ConfigChangeOrigin
+ public int value() {
+ return mValue;
+ }
+ }
+
private XmlResourceParser getDefaultConfigParser() throws IOException, XmlPullParserException {
String xml = "<zen version=\"10\">\n"
+ "<allow alarms=\"true\" media=\"true\" system=\"false\" calls=\"true\" "
@@ -2898,6 +2919,72 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ public void setManualZenMode_off_snoozesActiveRules(@TestParameter ChangeOrigin setZenOrigin) {
+ // Start with an active rule and an inactive rule.
+ mZenModeHelper.mConfig.automaticRules.clear();
+ AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ activeRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ inactiveRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ // User turns DND off.
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, setZenOrigin.value(),
+ "snoozing", "systemui", Process.SYSTEM_UID);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(activeRuleId).snoozing).isTrue();
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(inactiveRuleId).snoozing).isFalse();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void setManualZenMode_off_doesNotSnoozeRulesIfFromUser(
+ @TestParameter ChangeOrigin setZenOrigin) {
+ // Start with an active rule and an inactive rule
+ mZenModeHelper.mConfig.automaticRules.clear();
+ AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ activeRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ inactiveRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ // User turns DND off.
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, setZenOrigin.value(),
+ "snoozing", "systemui", Process.SYSTEM_UID);
+ ZenModeConfig config = mZenModeHelper.mConfig;
+ if (setZenOrigin == ChangeOrigin.ORIGIN_USER) {
+ // Other rule was unaffected.
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertThat(config.automaticRules.get(activeRuleId).snoozing).isFalse();
+ assertThat(config.automaticRules.get(inactiveRuleId).snoozing).isFalse();
+ } else {
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ assertThat(config.automaticRules.get(activeRuleId).snoozing).isTrue();
+ assertThat(config.automaticRules.get(inactiveRuleId).snoozing).isFalse();
+ }
+ }
+
+ @Test
public void testSetManualZenMode_legacy() {
setupZenConfig();