Announce private profile screenshot saving.
Extract announcement logic behind a flag (not much to extract, but it's
one less thing in the controller and now can be tested).
Test: atest com.android.systemui.screenshot
Flag: com.android.systemui.screenshot_private_profile_accessibility_announcement
Bug: 326941376
Change-Id: Iec464e4487e14a5a44298521e18d5b895eb27e90
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index f3e2272..f937c09 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -523,6 +523,16 @@
}
flag {
+ name: "screenshot_private_profile_accessibility_announcement_fix"
+ namespace: "systemui"
+ description: "Modified a11y announcement for private space screenshots"
+ bug: "326941376"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "screenshot_private_profile_behavior_fix"
namespace: "systemui"
description: "Private profile support for screenshots"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6df48a0..abfdc2a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -214,6 +214,8 @@
<string name="screenshot_saving_title">Saving screenshot\u2026</string>
<!-- Informs the user that a screenshot is being saved. [CHAR LIMIT=50] -->
<string name="screenshot_saving_work_profile_title">Saving screenshot to work profile\u2026</string>
+ <!-- Informs the user that a screenshot is being saved to the private profile. [CHAR LIMIT=100] -->
+ <string name="screenshot_saving_private_profile">Saving screenshot to private</string>
<!-- Notification title displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=50] -->
<string name="screenshot_saved_title">Screenshot saved</string>
<!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/AnnouncementResolver.kt b/packages/SystemUI/src/com/android/systemui/screenshot/AnnouncementResolver.kt
new file mode 100644
index 0000000..746f0f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/AnnouncementResolver.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.systemui.screenshot
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.android.systemui.screenshot.resources.Messages
+import java.util.function.Consumer
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** Logic for determining the announcement that a screenshot has been taken (for accessibility). */
+class AnnouncementResolver
+@Inject
+constructor(
+ private val messages: Messages,
+ private val profileTypes: ProfileTypeRepository,
+ @Application private val mainScope: CoroutineScope,
+) {
+
+ suspend fun getScreenshotAnnouncement(userId: Int): String =
+ when (profileTypes.getProfileType(userId)) {
+ ProfileType.PRIVATE -> messages.savingToPrivateProfileAnnouncement
+ ProfileType.WORK -> messages.savingToWorkProfileAnnouncement
+ else -> messages.savingScreenshotAnnouncement
+ }
+
+ fun getScreenshotAnnouncement(userId: Int, announceCallback: Consumer<String>) {
+ mainScope.launch { announceCallback.accept(getScreenshotAnnouncement(userId)) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 9ad6d0f..1a11e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -19,6 +19,7 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+import static com.android.systemui.Flags.screenshotPrivateProfileAccessibilityAnnouncementFix;
import static com.android.systemui.Flags.screenshotShelfUi2;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
@@ -217,6 +218,7 @@
private final AssistContentRequester mAssistContentRequester;
private final MessageContainerController mMessageContainerController;
+ private final AnnouncementResolver mAnnouncementResolver;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
@@ -268,6 +270,7 @@
AssistContentRequester assistContentRequester,
MessageContainerController messageContainerController,
Provider<ScreenshotSoundController> screenshotSoundController,
+ AnnouncementResolver announcementResolver,
@Assisted Display display,
@Assisted boolean showUIOnExternalDisplay
) {
@@ -297,6 +300,7 @@
mUserManager = userManager;
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
+ mAnnouncementResolver = announcementResolver;
mViewProxy = viewProxyFactory.getProxy(mContext, mDisplay.getDisplayId());
@@ -460,12 +464,20 @@
void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) {
withWindowAttached(() -> {
- if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
- mViewProxy.announceForAccessibility(mContext.getResources().getString(
- R.string.screenshot_saving_work_profile_title));
+ if (screenshotPrivateProfileAccessibilityAnnouncementFix()) {
+ mAnnouncementResolver.getScreenshotAnnouncement(
+ screenshot.getUserHandle().getIdentifier(),
+ announcement -> {
+ mViewProxy.announceForAccessibility(announcement);
+ });
} else {
- mViewProxy.announceForAccessibility(
- mContext.getResources().getString(R.string.screenshot_saving_title));
+ if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
+ mViewProxy.announceForAccessibility(mContext.getResources().getString(
+ R.string.screenshot_saving_work_profile_title));
+ } else {
+ mViewProxy.announceForAccessibility(
+ mContext.getResources().getString(R.string.screenshot_saving_title));
+ }
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/resources/Messages.kt b/packages/SystemUI/src/com/android/systemui/screenshot/resources/Messages.kt
new file mode 100644
index 0000000..fb10168
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/resources/Messages.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.systemui.screenshot.resources
+
+import android.content.Context
+import androidx.annotation.OpenForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** String values from resources, for easy injection. */
+@OpenForTesting
+@SysUISingleton
+open class Messages @Inject constructor(private val context: Context) {
+ open val savingScreenshotAnnouncement by lazy {
+ requireNotNull(context.resources.getString(R.string.screenshot_saving_title))
+ }
+
+ open val savingToWorkProfileAnnouncement by lazy {
+ requireNotNull(context.resources.getString(R.string.screenshot_saving_work_profile_title))
+ }
+
+ open val savingToPrivateProfileAnnouncement by lazy {
+ requireNotNull(context.resources.getString(R.string.screenshot_saving_private_profile))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/AnnouncementResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/AnnouncementResolverTest.kt
new file mode 100644
index 0000000..2e8498a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/AnnouncementResolverTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.systemui.screenshot
+
+import android.testing.AndroidTestingRunner
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.screenshot.data.repository.profileTypeRepository
+import com.android.systemui.screenshot.policy.TestUserIds
+import com.android.systemui.screenshot.resources.Messages
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@RunWith(AndroidTestingRunner::class)
+class AnnouncementResolverTest {
+ private val kosmos = Kosmos()
+
+ private val screenshotMessage = "Saving screenshot"
+ private val workMessage = "Saving to work profile"
+ private val privateMessage = "Saving to private profile"
+
+ private val messages =
+ mock(Messages::class.java).also {
+ whenever(it.savingScreenshotAnnouncement).thenReturn(screenshotMessage)
+ whenever(it.savingToWorkProfileAnnouncement).thenReturn(workMessage)
+ whenever(it.savingToPrivateProfileAnnouncement).thenReturn(privateMessage)
+ }
+
+ private val announcementResolver =
+ AnnouncementResolver(
+ messages,
+ kosmos.profileTypeRepository,
+ TestScope(UnconfinedTestDispatcher())
+ )
+
+ @Test
+ fun personalProfile() = runTest {
+ assertThat(announcementResolver.getScreenshotAnnouncement(TestUserIds.PERSONAL))
+ .isEqualTo(screenshotMessage)
+ }
+
+ @Test
+ fun workProfile() = runTest {
+ assertThat(announcementResolver.getScreenshotAnnouncement(TestUserIds.WORK))
+ .isEqualTo(workMessage)
+ }
+
+ @Test
+ fun privateProfile() = runTest {
+ assertThat(announcementResolver.getScreenshotAnnouncement(TestUserIds.PRIVATE))
+ .isEqualTo(privateMessage)
+ }
+}