Merge "Allow app protection role to uninstall packages" into main
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index abb562d..d8142fd 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1042,10 +1042,11 @@
}
/**
- * Get the available info about the AppWidget.
+ * Returns the {@link AppWidgetProviderInfo} for the specified AppWidget.
*
- * @return A appWidgetId. If the appWidgetId has not been bound to a provider yet, or
- * you don't have access to that appWidgetId, null is returned.
+ * @return Information regarding the provider of speficied widget, returns null if the
+ * appWidgetId has not been bound to a provider yet, or you don't have access
+ * to that widget.
*/
public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
if (mService == null) {
@@ -1390,7 +1391,7 @@
*
* @param provider The {@link ComponentName} for the {@link
* android.content.BroadcastReceiver BroadcastReceiver} provider for your AppWidget.
- * @param extras In not null, this is passed to the launcher app. For eg {@link
+ * @param extras IF not null, this is passed to the launcher app. e.g. {@link
* #EXTRA_APPWIDGET_PREVIEW} can be used for a custom preview.
* @param successCallback If not null, this intent will be sent when the widget is created.
*
diff --git a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
index 89c7582..d9e90fa 100644
--- a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
+++ b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
@@ -29,6 +29,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
+import android.widget.TextView;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -104,6 +105,26 @@
}
@Test
+ public void testDirectoryEnabledFlag() {
+ assertThat(mResources.getBoolean(R.bool.bool8)).isTrue();
+ }
+
+ @Test
+ public void testDirectoryDisabledFlag() {
+ assertThat(mResources.getBoolean(R.bool.bool7)).isTrue();
+ }
+
+ @Test
+ public void testDirectoryNegatedEnabledFlag() {
+ assertThat(mResources.getBoolean(R.bool.bool9)).isTrue();
+ }
+
+ @Test
+ public void testDirectoryNegatedDisabledFlag() {
+ assertThat(mResources.getBoolean(R.bool.bool10)).isTrue();
+ }
+
+ @Test
public void testLayoutWithDisabledElements() {
LinearLayout ll = (LinearLayout) getLayoutInflater().inflate(R.layout.layout1, null);
assertThat(ll).isNotNull();
@@ -112,6 +133,24 @@
assertThat((View) ll.findViewById(R.id.text2)).isNotNull();
}
+ @Test
+ public void testEnabledFlagLayoutOverrides() {
+ LinearLayout ll = (LinearLayout) getLayoutInflater().inflate(R.layout.layout3, null);
+ assertThat(ll).isNotNull();
+ assertThat((View) ll.findViewById(R.id.text1)).isNotNull();
+ assertThat(((TextView) ll.findViewById(R.id.text1)).getText()).isEqualTo("foobar");
+ }
+
+ @Test(expected = Resources.NotFoundException.class)
+ public void testDisabledLayout() {
+ getLayoutInflater().inflate(R.layout.layout2, null);
+ }
+
+ @Test(expected = Resources.NotFoundException.class)
+ public void testDisabledDrawable() {
+ mResources.getDrawable(R.drawable.removedpng);
+ }
+
private LayoutInflater getLayoutInflater() {
ContextWrapper c = new ContextWrapper(mContext) {
private LayoutInflater mInflater;
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 8b772d8..303a6f0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -91,9 +91,21 @@
to(Overlays.NotificationsShade) { toNotificationsShadeTransition() }
to(Overlays.QuickSettingsShade) { toQuickSettingsShadeTransition() }
- from(Overlays.NotificationsShade, Overlays.QuickSettingsShade) {
+ from(Overlays.NotificationsShade, to = Overlays.QuickSettingsShade) {
notificationsShadeToQuickSettingsShadeTransition()
}
+ from(Scenes.Gone, to = Overlays.NotificationsShade, key = SlightlyFasterShadeCollapse) {
+ toNotificationsShadeTransition(durationScale = 0.9)
+ }
+ from(Scenes.Gone, to = Overlays.QuickSettingsShade, key = SlightlyFasterShadeCollapse) {
+ toQuickSettingsShadeTransition(durationScale = 0.9)
+ }
+ from(Scenes.Lockscreen, to = Overlays.NotificationsShade, key = SlightlyFasterShadeCollapse) {
+ toNotificationsShadeTransition(durationScale = 0.9)
+ }
+ from(Scenes.Lockscreen, to = Overlays.QuickSettingsShade, key = SlightlyFasterShadeCollapse) {
+ toQuickSettingsShadeTransition(durationScale = 0.9)
+ }
// Scene overscroll
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index a08fbbf..fa304c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -198,6 +198,13 @@
@DisableFlags(Flags.FLAG_SCENE_CONTAINER)
fun testTransitionToGlanceableHubOnWake() =
testScope.runTest {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ reset(transitionRepository)
+
whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
kosmos.setCommunalAvailable(true)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
index 88a1df1..3388c75 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.notifications.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -25,6 +26,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayContentViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -36,13 +38,14 @@
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
+@EnableFlags(DualShade.FLAG_NAME)
class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor = kosmos.sceneInteractor
- private val underTest = kosmos.notificationsShadeOverlayContentViewModel
+ private val underTest by lazy { kosmos.notificationsShadeOverlayContentViewModel }
@Test
fun onScrimClicked_hidesShade() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
index abd1e2c..8c7ec47 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -25,6 +26,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -35,13 +37,14 @@
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
+@EnableFlags(DualShade.FLAG_NAME)
class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor = kosmos.sceneInteractor
- private val underTest = kosmos.quickSettingsShadeOverlayContentViewModel
+ private val underTest by lazy { kosmos.quickSettingsShadeOverlayContentViewModel }
@Test
fun onScrimClicked_hidesShade() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index d163abf..19ac0cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -287,7 +287,7 @@
@Test
fun anyExpansion_shadeGreater() =
- testScope.runTest() {
+ testScope.runTest {
// WHEN shade is more expanded than QS
shadeTestUtil.setShadeAndQsExpansion(.5f, 0f)
runCurrent()
@@ -298,7 +298,7 @@
@Test
fun anyExpansion_qsGreater() =
- testScope.runTest() {
+ testScope.runTest {
// WHEN qs is more expanded than shade
shadeTestUtil.setShadeAndQsExpansion(0f, .5f)
runCurrent()
@@ -308,6 +308,36 @@
}
@Test
+ fun isShadeAnyExpanded_shadeCollapsed() =
+ testScope.runTest {
+ val isShadeAnyExpanded by collectLastValue(underTest.isShadeAnyExpanded)
+ shadeTestUtil.setShadeExpansion(0f)
+ runCurrent()
+
+ assertThat(isShadeAnyExpanded).isFalse()
+ }
+
+ @Test
+ fun isShadeAnyExpanded_shadePartiallyExpanded() =
+ testScope.runTest {
+ val isShadeAnyExpanded by collectLastValue(underTest.isShadeAnyExpanded)
+ shadeTestUtil.setShadeExpansion(0.01f)
+ runCurrent()
+
+ assertThat(isShadeAnyExpanded).isTrue()
+ }
+
+ @Test
+ fun isShadeAnyExpanded_shadeFullyExpanded() =
+ testScope.runTest {
+ val isShadeAnyExpanded by collectLastValue(underTest.isShadeAnyExpanded)
+ shadeTestUtil.setShadeExpansion(1f)
+ runCurrent()
+
+ assertThat(isShadeAnyExpanded).isTrue()
+ }
+
+ @Test
fun isShadeTouchable_isFalse_whenDeviceAsleepAndNotPulsing() =
testScope.runTest {
powerRepository.updateWakefulness(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
index 109cd05..4592b60 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
@@ -35,6 +35,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
@@ -382,4 +383,44 @@
// THEN user is not interacting
assertThat(actual).isFalse()
}
+
+ @Test
+ fun expandNotificationsShade_unsupported() =
+ testScope.runTest {
+ assertThrows(UnsupportedOperationException::class.java) {
+ underTest.expandNotificationsShade("reason")
+ }
+ }
+
+ @Test
+ fun expandQuickSettingsShade_unsupported() =
+ testScope.runTest {
+ assertThrows(UnsupportedOperationException::class.java) {
+ underTest.expandQuickSettingsShade("reason")
+ }
+ }
+
+ @Test
+ fun collapseNotificationsShade_unsupported() =
+ testScope.runTest {
+ assertThrows(UnsupportedOperationException::class.java) {
+ underTest.collapseNotificationsShade("reason")
+ }
+ }
+
+ @Test
+ fun collapseQuickSettingsShade_unsupported() =
+ testScope.runTest {
+ assertThrows(UnsupportedOperationException::class.java) {
+ underTest.collapseQuickSettingsShade("reason")
+ }
+ }
+
+ @Test
+ fun collapseEitherShade_unsupported() =
+ testScope.runTest {
+ assertThrows(UnsupportedOperationException::class.java) {
+ underTest.collapseEitherShade("reason")
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index f6fe667ff..eb8ea8b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -1,12 +1,15 @@
package com.android.systemui.shade.ui.viewmodel
import android.content.Intent
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.provider.AlarmClock
import android.provider.Settings
import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -18,7 +21,9 @@
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
import com.android.systemui.testKosmos
@@ -48,7 +53,7 @@
private val sceneInteractor = kosmos.sceneInteractor
private val deviceEntryInteractor = kosmos.deviceEntryInteractor
- private val underTest: ShadeHeaderViewModel = kosmos.shadeHeaderViewModel
+ private val underTest by lazy { kosmos.shadeHeaderViewModel }
@Before
fun setUp() {
@@ -96,6 +101,7 @@
}
@Test
+ @DisableFlags(DualShade.FLAG_NAME)
fun onSystemIconContainerClicked_locked_collapsesShadeToLockscreen() =
testScope.runTest {
setDeviceEntered(false)
@@ -108,6 +114,25 @@
}
@Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun onSystemIconContainerClicked_lockedOnDualShade_collapsesShadeToLockscreen() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ setDeviceEntered(false)
+ setScene(Scenes.Lockscreen)
+ setOverlay(Overlays.NotificationsShade)
+ assertThat(currentOverlays).isNotEmpty()
+
+ underTest.onSystemIconContainerClicked()
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).isEmpty()
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
fun onSystemIconContainerClicked_unlocked_collapsesShadeToGone() =
testScope.runTest {
setDeviceEntered(true)
@@ -119,6 +144,24 @@
assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
}
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun onSystemIconContainerClicked_unlockedOnDualShade_collapsesShadeToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ setDeviceEntered(true)
+ setScene(Scenes.Gone)
+ setOverlay(Overlays.NotificationsShade)
+ assertThat(currentOverlays).isNotEmpty()
+
+ underTest.onSystemIconContainerClicked()
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(currentOverlays).isEmpty()
+ }
+
companion object {
private val SUB_1 =
SubscriptionModel(
@@ -144,6 +187,17 @@
testScope.runCurrent()
}
+ private fun setOverlay(key: OverlayKey) {
+ val currentOverlays = sceneInteractor.currentOverlays.value + key
+ sceneInteractor.showOverlay(key, "test")
+ sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(sceneInteractor.currentScene.value, currentOverlays)
+ )
+ )
+ testScope.runCurrent()
+ }
+
private fun TestScope.setDeviceEntered(isEntered: Boolean) {
if (isEntered) {
// Unlock the device marking the device has entered.
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
index 5be225c..219e45c 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
@@ -16,8 +16,7 @@
package com.android.systemui.notifications.ui.viewmodel
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import dagger.assisted.AssistedFactory
@@ -34,13 +33,10 @@
constructor(
val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
- private val sceneInteractor: SceneInteractor,
+ private val shadeInteractor: ShadeInteractor,
) {
fun onScrimClicked() {
- sceneInteractor.hideOverlay(
- overlay = Overlays.NotificationsShade,
- loggingReason = "Shade scrim clicked",
- )
+ shadeInteractor.collapseNotificationsShade(loggingReason = "Shade scrim clicked")
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
index 3b97d82..7c8fbea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
@@ -16,8 +16,7 @@
package com.android.systemui.qs.ui.viewmodel
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -31,15 +30,12 @@
class QuickSettingsShadeOverlayContentViewModel
@AssistedInject
constructor(
- val sceneInteractor: SceneInteractor,
+ val shadeInteractor: ShadeInteractor,
val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
) {
fun onScrimClicked() {
- sceneInteractor.hideOverlay(
- overlay = Overlays.QuickSettingsShade,
- loggingReason = "Shade scrim clicked",
- )
+ shadeInteractor.collapseQuickSettingsShade(loggingReason = "Shade scrim clicked")
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ac49e91..559c263 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -248,7 +248,8 @@
} else if (action == ACTION_UP) {
// Gesture was too short to be picked up by scene container touch
// handling; programmatically start the transition to the shade.
- mShadeInteractor.get().expandNotificationShade("short launcher swipe");
+ mShadeInteractor.get()
+ .expandNotificationsShade("short launcher swipe", null);
}
}
event.recycle();
@@ -265,7 +266,8 @@
mSceneInteractor.get().onRemoteUserInputStarted(
"trackpad swipe");
} else if (action == ACTION_UP) {
- mShadeInteractor.get().expandNotificationShade("short trackpad swipe");
+ mShadeInteractor.get()
+ .expandNotificationsShade("short trackpad swipe", null);
}
mStatusBarWinController.getWindowRootView().dispatchTouchEvent(event);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
index b9f57f2..3c6d858 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
@@ -32,4 +32,7 @@
* normal collapse would.
*/
val SlightlyFasterShadeCollapse = TransitionKey("SlightlyFasterShadeCollapse")
+
+ /** Reference to a content transition that should happen instantly, i.e. without animation. */
+ val Instant = TransitionKey("Instant")
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 361226a4..6c99282 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -22,8 +22,8 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.Instant
import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -80,18 +80,25 @@
}
}
+ @Deprecated("Deprecated in Java")
override fun isShadeEnabled() = shadeInteractor.isShadeEnabled.value
+ @Deprecated("Deprecated in Java")
override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value
+ @Deprecated("Deprecated in Java")
override fun isExpandingOrCollapsing(): Boolean = shadeInteractor.isUserInteracting.value
+ @Deprecated("Deprecated in Java")
override fun instantExpandShade() {
// Do nothing
}
override fun instantCollapseShade() {
- sceneInteractor.snapToScene(SceneFamilies.Home, "hide shade")
+ shadeInteractor.collapseNotificationsShade(
+ loggingReason = "ShadeControllerSceneImpl.instantCollapseShade",
+ transitionKey = Instant,
+ )
}
override fun animateCollapseShade(
@@ -122,16 +129,17 @@
}
}
+ @Deprecated("Deprecated in Java")
override fun collapseWithDuration(animationDuration: Int) {
// TODO(b/300258424) inline this. The only caller uses the default duration.
animateCollapseShade()
}
private fun animateCollapseShadeInternal() {
- sceneInteractor.changeScene(
- SceneFamilies.Home, // TODO(b/336581871): add sceneState?
- "ShadeController.animateCollapseShade",
- SlightlyFasterShadeCollapse,
+ // TODO(b/336581871): add sceneState?
+ shadeInteractor.collapseEitherShade(
+ loggingReason = "ShadeController.animateCollapseShade",
+ transitionKey = SlightlyFasterShadeCollapse,
)
}
@@ -140,6 +148,7 @@
animateCollapseShade()
}
+ @Deprecated("Deprecated in Java")
override fun closeShadeIfOpen(): Boolean {
if (shadeInteractor.isAnyExpanded.value) {
commandQueue.animateCollapsePanels(
@@ -155,6 +164,7 @@
animateCollapseShadeForcedDelayed()
}
+ @Deprecated("Deprecated in Java")
override fun collapseShade(animate: Boolean) {
if (animate) {
animateCollapseShade()
@@ -163,13 +173,14 @@
}
}
+ @Deprecated("Deprecated in Java")
override fun collapseOnMainThread() {
// TODO if this works with delegation alone, we can deprecate and delete
collapseShade()
}
override fun expandToNotifications() {
- shadeInteractor.expandNotificationShade("ShadeController.animateExpandShade")
+ shadeInteractor.expandNotificationsShade("ShadeController.animateExpandShade")
}
override fun expandToQs() {
@@ -193,14 +204,17 @@
}
}
+ @Deprecated("Deprecated in Java")
override fun postAnimateCollapseShade() {
animateCollapseShade()
}
+ @Deprecated("Deprecated in Java")
override fun postAnimateForceCollapseShade() {
animateCollapseShadeForced()
}
+ @Deprecated("Deprecated in Java")
override fun postAnimateExpandQs() {
expandToQs()
}
@@ -214,18 +228,23 @@
}
}
+ @Deprecated("Deprecated in Java")
override fun makeExpandedInvisible() {
// Do nothing
}
+ @Deprecated("Deprecated in Java")
override fun makeExpandedVisible(force: Boolean) {
// Do nothing
}
+ @Deprecated("Deprecated in Java")
override fun isExpandedVisible(): Boolean {
- return sceneInteractor.currentScene.value != Scenes.Gone
+ return sceneInteractor.currentScene.value != Scenes.Gone ||
+ sceneInteractor.currentOverlays.value.isNotEmpty()
}
+ @Deprecated("Deprecated in Java")
override fun onStatusBarTouch(event: MotionEvent) {
// The only call to this doesn't happen with MigrateClocksToBlueprint.isEnabled enabled
throw UnsupportedOperationException()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index b046c50..a3f2c64 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import androidx.annotation.FloatRange
+import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.shade.shared.model.ShadeMode
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -27,19 +28,22 @@
/** Business logic for shade interactions. */
interface ShadeInteractor : BaseShadeInteractor {
- /** Emits true if the shade is currently allowed and false otherwise. */
+ /** Emits true if the Notifications shade is currently allowed and false otherwise. */
val isShadeEnabled: StateFlow<Boolean>
- /** Emits true if QS is currently allowed and false otherwise. */
+ /** Emits true if QS shade is currently allowed and false otherwise. */
val isQsEnabled: StateFlow<Boolean>
- /** Whether either the shade or QS is fully expanded. */
+ /** Whether either the Notifications shade or QS shade is fully expanded. */
val isAnyFullyExpanded: StateFlow<Boolean>
- /** Whether the Shade is fully expanded. */
+ /** Whether the Notifications Shade is fully expanded. */
val isShadeFullyExpanded: Flow<Boolean>
- /** Whether the Shade is fully collapsed. */
+ /** Whether Notifications Shade is expanded a non-zero amount. */
+ val isShadeAnyExpanded: StateFlow<Boolean>
+
+ /** Whether the Notifications Shade is fully collapsed. */
val isShadeFullyCollapsed: Flow<Boolean>
/**
@@ -102,7 +106,7 @@
*/
val isAnyExpanded: StateFlow<Boolean>
- /** The amount [0-1] that the shade has been opened. */
+ /** The amount [0-1] that the Notifications Shade has been opened. */
val shadeExpansion: StateFlow<Float>
/**
@@ -111,7 +115,7 @@
*/
val qsExpansion: StateFlow<Float>
- /** Whether Quick Settings is expanded a non-zero amount. */
+ /** Whether Quick Settings Shade is expanded a non-zero amount. */
val isQsExpanded: StateFlow<Boolean>
/**
@@ -142,16 +146,38 @@
val isUserInteractingWithQs: Flow<Boolean>
/**
- * Triggers the expansion (opening) of the notification shade. If the notification shade is
- * already open, this has no effect.
+ * Triggers the expansion (opening) of the notifications shade. If it is already expanded, this
+ * has no effect.
*/
- fun expandNotificationShade(loggingReason: String)
+ fun expandNotificationsShade(loggingReason: String, transitionKey: TransitionKey? = null)
/**
- * Triggers the expansion (opening) of the quick settings shade. If the quick settings shade is
- * already open, this has no effect.
+ * Triggers the expansion (opening) of the quick settings shade. If it is already expanded, this
+ * has no effect.
*/
- fun expandQuickSettingsShade(loggingReason: String)
+ fun expandQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey? = null)
+
+ /**
+ * Triggers the collapse (closing) of the notifications shade. If it is already collapsed, this
+ * has no effect.
+ */
+ fun collapseNotificationsShade(loggingReason: String, transitionKey: TransitionKey? = null)
+
+ /**
+ * Triggers the collapse (closing) of the quick settings shade. If it is already collapsed, this
+ * has no effect.
+ */
+ fun collapseQuickSettingsShade(
+ loggingReason: String,
+ transitionKey: TransitionKey? = null,
+ bypassNotificationsShade: Boolean = false,
+ )
+
+ /**
+ * Triggers the collapse (closing) of the notifications shade or quick settings shade, whichever
+ * is open. If both are already collapsed, this has no effect.
+ */
+ fun collapseEitherShade(loggingReason: String, transitionKey: TransitionKey? = null)
}
fun createAnyExpansionFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index fb14828..322fca3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.domain.interactor
+import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
@@ -31,6 +32,7 @@
override val isShadeEnabled: StateFlow<Boolean> = inactiveFlowBoolean
override val isQsEnabled: StateFlow<Boolean> = inactiveFlowBoolean
override val shadeExpansion: StateFlow<Float> = inactiveFlowFloat
+ override val isShadeAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
override val qsExpansion: StateFlow<Float> = inactiveFlowFloat
override val isQsExpanded: StateFlow<Boolean> = inactiveFlowBoolean
override val isQsBypassingShade: Flow<Boolean> = inactiveFlowBoolean
@@ -50,7 +52,17 @@
override fun getTopEdgeSplitFraction(): Float = 0.5f
- override fun expandNotificationShade(loggingReason: String) {}
+ override fun expandNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {}
- override fun expandQuickSettingsShade(loggingReason: String) {}
+ override fun expandQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey?) {}
+
+ override fun collapseNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {}
+
+ override fun collapseQuickSettingsShade(
+ loggingReason: String,
+ transitionKey: TransitionKey?,
+ bypassNotificationsShade: Boolean,
+ ) {}
+
+ override fun collapseEitherShade(loggingReason: String, transitionKey: TransitionKey?) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 3eab02a..949d2aa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -78,12 +78,16 @@
override val isShadeFullyExpanded: Flow<Boolean> =
baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged()
+ override val isShadeAnyExpanded: StateFlow<Boolean> =
+ baseShadeInteractor.shadeExpansion
+ .map { it > 0 }
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
override val isShadeFullyCollapsed: Flow<Boolean> =
baseShadeInteractor.shadeExpansion.map { it <= 0f }.distinctUntilChanged()
override val isUserInteracting: StateFlow<Boolean> =
combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs }
- .distinctUntilChanged()
.stateIn(scope, SharingStarted.Eagerly, false)
override val isShadeTouchable: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index df09486..0902c39 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.app.tracing.FlowTracing.traceAsCounter
+import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -111,18 +112,40 @@
override val isUserInteractingWithQs: Flow<Boolean> =
userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
- override fun expandNotificationShade(loggingReason: String) {
+ override fun expandNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {
throw UnsupportedOperationException(
"expandNotificationShade() is not supported in legacy shade"
)
}
- override fun expandQuickSettingsShade(loggingReason: String) {
+ override fun expandQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey?) {
throw UnsupportedOperationException(
"expandQuickSettingsShade() is not supported in legacy shade"
)
}
+ override fun collapseNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {
+ throw UnsupportedOperationException(
+ "collapseNotificationShade() is not supported in legacy shade"
+ )
+ }
+
+ override fun collapseQuickSettingsShade(
+ loggingReason: String,
+ transitionKey: TransitionKey?,
+ bypassNotificationsShade: Boolean,
+ ) {
+ throw UnsupportedOperationException(
+ "collapseQuickSettingsShade() is not supported in legacy shade"
+ )
+ }
+
+ override fun collapseEitherShade(loggingReason: String, transitionKey: TransitionKey?) {
+ throw UnsupportedOperationException(
+ "collapseEitherShade() is not supported in legacy shade"
+ )
+ }
+
/**
* Return a flow for whether a user is interacting with an expandable shade component using
* tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 81bf712..7658108 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -21,12 +21,16 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.Instant
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
@@ -133,43 +137,120 @@
}
}
- override fun expandNotificationShade(loggingReason: String) {
+ override fun expandNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {
if (shadeModeInteractor.isDualShade) {
if (Overlays.QuickSettingsShade in sceneInteractor.currentOverlays.value) {
sceneInteractor.replaceOverlay(
from = Overlays.QuickSettingsShade,
to = Overlays.NotificationsShade,
loggingReason = loggingReason,
+ transitionKey = transitionKey,
)
} else {
sceneInteractor.showOverlay(
overlay = Overlays.NotificationsShade,
loggingReason = loggingReason,
+ transitionKey = transitionKey,
)
}
} else {
- sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = loggingReason)
+ sceneInteractor.changeScene(
+ toScene = Scenes.Shade,
+ loggingReason = loggingReason,
+ transitionKey =
+ transitionKey ?: ToSplitShade.takeIf { shadeModeInteractor.isSplitShade },
+ )
}
}
- override fun expandQuickSettingsShade(loggingReason: String) {
+ override fun expandQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey?) {
if (shadeModeInteractor.isDualShade) {
if (Overlays.NotificationsShade in sceneInteractor.currentOverlays.value) {
sceneInteractor.replaceOverlay(
from = Overlays.NotificationsShade,
to = Overlays.QuickSettingsShade,
loggingReason = loggingReason,
+ transitionKey = transitionKey,
)
} else {
sceneInteractor.showOverlay(
overlay = Overlays.QuickSettingsShade,
loggingReason = loggingReason,
+ transitionKey = transitionKey,
)
}
} else {
+ val isSplitShade = shadeModeInteractor.isSplitShade
sceneInteractor.changeScene(
- toScene = Scenes.QuickSettings,
+ toScene = if (isSplitShade) Scenes.Shade else Scenes.QuickSettings,
loggingReason = loggingReason,
+ transitionKey = transitionKey ?: ToSplitShade.takeIf { isSplitShade },
+ )
+ }
+ }
+
+ override fun collapseNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {
+ if (shadeModeInteractor.isDualShade) {
+ // TODO(b/356596436): Hide without animation if transitionKey is Instant.
+ sceneInteractor.hideOverlay(
+ overlay = Overlays.NotificationsShade,
+ loggingReason = loggingReason,
+ transitionKey = transitionKey,
+ )
+ } else if (transitionKey == Instant) {
+ // TODO(b/356596436): Define instant transition instead of snapToScene().
+ sceneInteractor.snapToScene(toScene = SceneFamilies.Home, loggingReason = loggingReason)
+ } else {
+ sceneInteractor.changeScene(
+ toScene = SceneFamilies.Home,
+ loggingReason = loggingReason,
+ transitionKey =
+ transitionKey ?: ToSplitShade.takeIf { shadeModeInteractor.isSplitShade },
+ )
+ }
+ }
+
+ override fun collapseQuickSettingsShade(
+ loggingReason: String,
+ transitionKey: TransitionKey?,
+ bypassNotificationsShade: Boolean,
+ ) {
+ if (shadeModeInteractor.isDualShade) {
+ // TODO(b/356596436): Hide without animation if transitionKey is Instant.
+ sceneInteractor.hideOverlay(
+ overlay = Overlays.QuickSettingsShade,
+ loggingReason = loggingReason,
+ transitionKey = transitionKey,
+ )
+ return
+ }
+
+ val isSplitShade = shadeModeInteractor.isSplitShade
+ val targetScene =
+ if (bypassNotificationsShade || isSplitShade) SceneFamilies.Home else Scenes.Shade
+ if (transitionKey == Instant) {
+ // TODO(b/356596436): Define instant transition instead of snapToScene().
+ sceneInteractor.snapToScene(toScene = targetScene, loggingReason = loggingReason)
+ } else {
+ sceneInteractor.changeScene(
+ toScene = targetScene,
+ loggingReason = loggingReason,
+ transitionKey = transitionKey ?: ToSplitShade.takeIf { isSplitShade },
+ )
+ }
+ }
+
+ override fun collapseEitherShade(loggingReason: String, transitionKey: TransitionKey?) {
+ // Note: The notifications shade and QS shade may be both partially expanded simultaneously,
+ // so we don't use an 'else' clause here.
+ if (shadeExpansion.value > 0) {
+ collapseNotificationsShade(loggingReason = loggingReason, transitionKey = transitionKey)
+ }
+ if (isQsExpanded.value) {
+ collapseQuickSettingsShade(
+ loggingReason = loggingReason,
+ transitionKey = transitionKey,
+ bypassNotificationsShade = true,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 0fb3790..ea76ac4b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -21,10 +21,9 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.Instant
import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -48,7 +47,7 @@
@Deprecated("Use ShadeInteractor instead")
override fun expandToNotifications() {
- shadeInteractor.expandNotificationShade(
+ shadeInteractor.expandNotificationsShade(
loggingReason = "ShadeLockscreenInteractorImpl.expandToNotifications"
)
}
@@ -71,17 +70,11 @@
}
override fun resetViews(animate: Boolean) {
- val loggingReason = "ShadeLockscreenInteractorImpl.resetViews"
// The existing comment to the only call to this claims it only calls it to collapse QS
- if (shadeInteractor.shadeMode.value == ShadeMode.Dual) {
- // TODO(b/356596436): Hide without animation if !animate.
- sceneInteractor.hideOverlay(
- overlay = Overlays.QuickSettingsShade,
- loggingReason = loggingReason,
- )
- } else {
- shadeInteractor.expandNotificationShade(loggingReason)
- }
+ shadeInteractor.collapseQuickSettingsShade(
+ loggingReason = "ShadeLockscreenInteractorImpl.resetViews",
+ transitionKey = Instant.takeIf { !animate },
+ )
}
@Deprecated("Not supported by scenes")
@@ -93,7 +86,7 @@
backgroundScope.launch {
delay(delay)
withContext(mainDispatcher) {
- shadeInteractor.expandNotificationShade(
+ shadeInteractor.expandNotificationsShade(
"ShadeLockscreenInteractorImpl.transitionToExpandedShade"
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
index caa4513..c838c37 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
@@ -55,6 +55,10 @@
val isDualShade: Boolean
get() = shadeMode.value is ShadeMode.Dual
+ /** Convenience shortcut for querying whether the current [shadeMode] is [ShadeMode.Split]. */
+ val isSplitShade: Boolean
+ get() = shadeMode.value is ShadeMode.Split
+
/**
* The fraction between [0..1] (i.e., percentage) of screen width to consider the threshold
* between "top-left" and "top-right" for the purposes of dual-shade invocation.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index a154e91..bd4ed5b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -29,9 +29,7 @@
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItem
import com.android.systemui.res.R
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.shared.model.TransitionKeys
+import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor
import com.android.systemui.shade.domain.interactor.ShadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -55,9 +53,8 @@
class ShadeHeaderViewModel
@AssistedInject
constructor(
- private val context: Context,
+ context: Context,
private val activityStarter: ActivityStarter,
- private val sceneInteractor: SceneInteractor,
private val shadeInteractor: ShadeInteractor,
private val mobileIconsInteractor: MobileIconsInteractor,
val mobileIconsViewModel: MobileIconsViewModel,
@@ -120,7 +117,7 @@
map = { intent, _ ->
intent.action == Intent.ACTION_TIMEZONE_CHANGED ||
intent.action == Intent.ACTION_LOCALE_CHANGED
- }
+ },
)
.onEach { invalidateFormats -> updateDateTexts(invalidateFormats) }
.launchIn(this)
@@ -152,10 +149,9 @@
/** Notifies that the system icons container was clicked. */
fun onSystemIconContainerClicked() {
- sceneInteractor.changeScene(
- SceneFamilies.Home,
- "ShadeHeaderViewModel.onSystemIconContainerClicked",
- TransitionKeys.SlightlyFasterShadeCollapse,
+ shadeInteractor.collapseEitherShade(
+ loggingReason = "ShadeHeaderViewModel.onSystemIconContainerClicked",
+ transitionKey = SlightlyFasterShadeCollapse,
)
}
@@ -163,7 +159,7 @@
fun onShadeCarrierGroupClicked() {
activityStarter.postStartActivityDismissingKeyguard(
Intent(Settings.ACTION_WIRELESS_SETTINGS),
- 0
+ 0,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index a1750cd..b1ec740 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -21,6 +21,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
@@ -28,6 +29,7 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
@@ -38,9 +40,9 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,14 +57,9 @@
private val configurationRepository = kosmos.fakeConfigurationRepository
private val keyguardRepository = kosmos.fakeKeyguardRepository
private val sceneInteractor = kosmos.sceneInteractor
- private val shadeTestUtil = kosmos.shadeTestUtil
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
- private lateinit var underTest: ShadeInteractorSceneContainerImpl
-
- @Before
- fun setUp() {
- underTest = kosmos.shadeInteractorSceneContainerImpl
- }
+ private val underTest by lazy { kosmos.shadeInteractorSceneContainerImpl }
@Test
fun qsExpansionWhenInSplitShadeAndQsExpanded() =
@@ -600,14 +597,14 @@
@Test
@EnableFlags(DualShade.FLAG_NAME)
- fun expandNotificationShade_dualShadeEnabled_opensOverlay() =
+ fun expandNotificationsShade_dualShade_opensOverlay() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
assertThat(currentOverlays).isEmpty()
- underTest.expandNotificationShade("reason")
+ underTest.expandNotificationsShade("reason")
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
@@ -615,14 +612,15 @@
@Test
@DisableFlags(DualShade.FLAG_NAME)
- fun expandNotificationShade_dualShadeDisabled_switchesToShadeScene() =
+ fun expandNotificationsShade_singleShade_switchesToShadeScene() =
testScope.runTest {
+ shadeTestUtil.setSplitShade(false)
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
assertThat(currentOverlays).isEmpty()
- underTest.expandNotificationShade("reason")
+ underTest.expandNotificationsShade("reason")
assertThat(currentScene).isEqualTo(Scenes.Shade)
assertThat(currentOverlays).isEmpty()
@@ -630,7 +628,7 @@
@Test
@EnableFlags(DualShade.FLAG_NAME)
- fun expandNotificationShade_dualShadeEnabledAndQuickSettingsOpen_replacesOverlay() =
+ fun expandNotificationsShade_dualShadeQuickSettingsOpen_replacesOverlay() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
@@ -638,14 +636,14 @@
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade)
- underTest.expandNotificationShade("reason")
+ underTest.expandNotificationsShade("reason")
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
}
@Test
@EnableFlags(DualShade.FLAG_NAME)
- fun expandQuickSettingsShade_dualShadeEnabled_opensOverlay() =
+ fun expandQuickSettingsShade_dualShade_opensOverlay() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
@@ -660,8 +658,9 @@
@Test
@DisableFlags(DualShade.FLAG_NAME)
- fun expandQuickSettingsShade_dualShadeDisabled_switchesToQuickSettingsScene() =
+ fun expandQuickSettingsShade_singleShade_switchesToQuickSettingsScene() =
testScope.runTest {
+ shadeTestUtil.setSplitShade(false)
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
@@ -674,12 +673,28 @@
}
@Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun expandQuickSettingsShade_splitShade_switchesToShadeScene() =
+ testScope.runTest {
+ shadeTestUtil.setSplitShade(true)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).isEmpty()
+
+ underTest.expandQuickSettingsShade("reason")
+
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(currentOverlays).isEmpty()
+ }
+
+ @Test
@EnableFlags(DualShade.FLAG_NAME)
- fun expandQuickSettingsShade_dualShadeEnabledAndNotificationsOpen_replacesOverlay() =
+ fun expandQuickSettingsShade_dualShadeNotificationsOpen_replacesOverlay() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
- underTest.expandNotificationShade("reason")
+ underTest.expandNotificationsShade("reason")
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
@@ -687,4 +702,141 @@
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade)
}
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun collapseNotificationsShade_dualShade_hidesOverlay() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ openShade(Overlays.NotificationsShade)
+
+ underTest.collapseNotificationsShade("reason")
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).isEmpty()
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun collapseNotificationsShade_singleShade_switchesToLockscreen() =
+ testScope.runTest {
+ shadeTestUtil.setSplitShade(false)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ sceneInteractor.changeScene(Scenes.Shade, "reason")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(currentOverlays).isEmpty()
+
+ underTest.collapseNotificationsShade("reason")
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun collapseQuickSettingsShade_dualShade_hidesOverlay() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ openShade(Overlays.QuickSettingsShade)
+
+ underTest.collapseQuickSettingsShade("reason")
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).isEmpty()
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun collapseQuickSettingsShadeNotBypassingShade_singleShade_switchesToShade() =
+ testScope.runTest {
+ shadeTestUtil.setSplitShade(false)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+ assertThat(currentOverlays).isEmpty()
+
+ underTest.collapseQuickSettingsShade(
+ loggingReason = "reason",
+ bypassNotificationsShade = false,
+ )
+
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(currentOverlays).isEmpty()
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun collapseQuickSettingsShadeNotBypassingShade_splitShade_switchesToLockscreen() =
+ testScope.runTest {
+ shadeTestUtil.setSplitShade(true)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+ assertThat(currentOverlays).isEmpty()
+
+ underTest.collapseQuickSettingsShade(
+ loggingReason = "reason",
+ bypassNotificationsShade = false,
+ )
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).isEmpty()
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun collapseQuickSettingsShadeBypassingShade_singleShade_switchesToLockscreen() =
+ testScope.runTest {
+ shadeTestUtil.setSplitShade(false)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+ assertThat(currentOverlays).isEmpty()
+
+ underTest.collapseQuickSettingsShade(
+ loggingReason = "reason",
+ bypassNotificationsShade = true,
+ )
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun collapseEitherShade_dualShade_hidesBothOverlays() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ openShade(Overlays.QuickSettingsShade)
+ openShade(Overlays.NotificationsShade)
+ assertThat(currentOverlays)
+ .containsExactly(Overlays.QuickSettingsShade, Overlays.NotificationsShade)
+
+ underTest.collapseEitherShade("reason")
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).isEmpty()
+ }
+
+ private fun TestScope.openShade(overlay: OverlayKey) {
+ val isAnyExpanded by collectLastValue(underTest.isAnyExpanded)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val initialScene = checkNotNull(currentScene)
+ sceneInteractor.showOverlay(overlay, "reason")
+ kosmos.setSceneTransition(
+ ObservableTransitionState.Idle(initialScene, checkNotNull(currentOverlays))
+ )
+ runCurrent()
+ assertThat(currentScene).isEqualTo(initialScene)
+ assertThat(currentOverlays).contains(overlay)
+ assertThat(isAnyExpanded).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
index ff8b478..a80a409 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
@@ -17,13 +17,13 @@
package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayContentViewModel by
Kosmos.Fixture {
QuickSettingsShadeOverlayContentViewModel(
- sceneInteractor = sceneInteractor,
+ shadeInteractor = shadeInteractor,
shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
quickSettingsContainerViewModel = quickSettingsContainerViewModel,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
index 6d488d2..60141c6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
@@ -25,6 +25,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.shade.data.repository.ShadeRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
@@ -133,7 +134,7 @@
class ShadeTestUtilLegacyImpl(
val testScope: TestScope,
val shadeRepository: FakeShadeRepository,
- val context: SysuiTestableContext
+ val context: SysuiTestableContext,
) : ShadeTestUtilDelegate {
override fun setShadeAndQsExpansion(shadeExpansion: Float, qsExpansion: Float) {
shadeRepository.setLegacyShadeExpansion(shadeExpansion)
@@ -191,6 +192,7 @@
}
/** Sets up shade state for tests when the scene container flag is enabled. */
+@OptIn(ExperimentalCoroutinesApi::class)
class ShadeTestUtilSceneImpl(
val testScope: TestScope,
val sceneInteractor: SceneInteractor,
@@ -269,7 +271,7 @@
from: SceneKey,
to: SceneKey,
progress: Float,
- isInitiatedByUserInput: Boolean = true
+ isInitiatedByUserInput: Boolean = true,
) {
sceneInteractor.changeScene(from, "test")
val transitionState =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
index 9cdd519..7a15fdf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
@@ -19,7 +19,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel
-import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModelFactory
val Kosmos.notificationsShadeOverlayContentViewModel:
@@ -27,6 +27,6 @@
NotificationsShadeOverlayContentViewModel(
shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
notificationsPlaceholderViewModelFactory = notificationsPlaceholderViewModelFactory,
- sceneInteractor = sceneInteractor,
+ shadeInteractor = shadeInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
index 7eb9f34..f5b856d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
@@ -20,7 +20,6 @@
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.activityStarter
-import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -32,7 +31,6 @@
ShadeHeaderViewModel(
context = applicationContext,
activityStarter = activityStarter,
- sceneInteractor = sceneInteractor,
shadeInteractor = shadeInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 3224b27..73b7b35 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -165,16 +165,27 @@
protected final AccessibilitySecurityPolicy mSecurityPolicy;
protected final AccessibilityTrace mTrace;
- // The attribution tag set by the service that is bound to this instance
+ /** The attribution tag set by the client that is bound to this instance */
protected String mAttributionTag;
protected int mDisplayTypes = DISPLAY_TYPE_DEFAULT;
- // The service that's bound to this instance. Whenever this value is non-null, this
- // object is registered as a death recipient
- IBinder mService;
+ /**
+ * Binder of the {@link #mClient}.
+ *
+ * <p>Whenever this value is non-null, it should be registered as a {@link
+ * IBinder.DeathRecipient}
+ */
+ @Nullable IBinder mClientBinder;
- IAccessibilityServiceClient mServiceInterface;
+ /**
+ * The accessibility client this class represents.
+ *
+ * <p>The client is in the application process, i.e., it's a client of system_server. Depending
+ * on the use case, the client can be an {@link AccessibilityService}, a {@code UiAutomation},
+ * etc.
+ */
+ @Nullable IAccessibilityServiceClient mClient;
int mEventTypes;
@@ -218,10 +229,10 @@
int mGenericMotionEventSources;
int mObservedMotionEventSources;
- // the events pending events to be dispatched to this service
+ /** Pending events to be dispatched to the client */
final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>();
- /** Whether this service relies on its {@link AccessibilityCache} being up to date */
+ /** Whether the client relies on its {@link AccessibilityCache} being up to date */
boolean mUsesAccessibilityCache = false;
// Handler only for dispatching accessibility events since we use event
@@ -230,7 +241,7 @@
final SparseArray<IBinder> mOverlayWindowTokens = new SparseArray();
- // All the embedded accessibility overlays that have been added by this service.
+ /** All the embedded accessibility overlays that have been added by the client. */
private List<SurfaceControl> mOverlays = new ArrayList<>();
/** The timestamp of requesting to take screenshot in milliseconds */
@@ -274,7 +285,8 @@
/**
* Called back to notify system that the client has changed
- * @param serviceInfoChanged True if the service's AccessibilityServiceInfo changed.
+ *
+ * @param serviceInfoChanged True if the client's AccessibilityServiceInfo changed.
*/
void onClientChangeLocked(boolean serviceInfoChanged);
@@ -360,21 +372,22 @@
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mIPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
- mEventDispatchHandler = new Handler(mainHandler.getLooper()) {
- @Override
- public void handleMessage(Message message) {
- final int eventType = message.what;
- AccessibilityEvent event = (AccessibilityEvent) message.obj;
- boolean serviceWantsEvent = message.arg1 != 0;
- notifyAccessibilityEventInternal(eventType, event, serviceWantsEvent);
- }
- };
+ mEventDispatchHandler =
+ new Handler(mainHandler.getLooper()) {
+ @Override
+ public void handleMessage(Message message) {
+ final int eventType = message.what;
+ AccessibilityEvent event = (AccessibilityEvent) message.obj;
+ boolean clientWantsEvent = message.arg1 != 0;
+ notifyAccessibilityEventInternal(eventType, event, clientWantsEvent);
+ }
+ };
setDynamicallyConfigurableProperties(accessibilityServiceInfo);
}
@Override
public boolean onKeyEvent(KeyEvent keyEvent, int sequenceNumber) {
- if (!mRequestFilterKeyEvents || (mServiceInterface == null)) {
+ if (!mRequestFilterKeyEvents || (mClient == null)) {
return false;
}
if((mAccessibilityServiceInfo.getCapabilities()
@@ -388,7 +401,7 @@
if (svcClientTracingEnabled()) {
logTraceSvcClient("onKeyEvent", keyEvent + ", " + sequenceNumber);
}
- mServiceInterface.onKeyEvent(keyEvent, sequenceNumber);
+ mClient.onKeyEvent(keyEvent, sequenceNumber);
} catch (RemoteException e) {
return false;
}
@@ -470,7 +483,7 @@
}
public boolean canReceiveEventsLocked() {
- return (mEventTypes != 0 && mService != null);
+ return (mEventTypes != 0 && mClientBinder != null);
}
@RequiresNoPermission
@@ -520,7 +533,7 @@
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- // If the XML manifest had data to configure the service its info
+ // If the XML manifest had data to configure the AccessibilityService, its info
// should be already set. In such a case update only the dynamically
// configurable properties.
boolean oldRequestIme = mRequestImeApis;
@@ -1733,40 +1746,40 @@
try {
// Clear the proxy in the other process so this
// IAccessibilityServiceConnection can be garbage collected.
- if (mServiceInterface != null) {
+ if (mClient != null) {
if (svcClientTracingEnabled()) {
logTraceSvcClient("init", "null, " + mId + ", null");
}
- mServiceInterface.init(null, mId, null);
+ mClient.init(null, mId, null);
}
} catch (RemoteException re) {
/* ignore */
}
- if (mService != null) {
+ if (mClientBinder != null) {
try {
- mService.unlinkToDeath(this, 0);
+ mClientBinder.unlinkToDeath(this, 0);
} catch (NoSuchElementException e) {
Slog.e(LOG_TAG, "Failed unregistering death link");
}
- mService = null;
+ mClientBinder = null;
}
- mServiceInterface = null;
+ mClient = null;
mReceivedAccessibilityButtonCallbackSinceBind = false;
}
public boolean isConnectedLocked() {
- return (mService != null);
+ return (mClientBinder != null);
}
public void notifyAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
final int eventType = event.getEventType();
- final boolean serviceWantsEvent = wantsEventLocked(event);
+ final boolean clientWantsEvent = clientWantsEventLocked(event);
final boolean requiredForCacheConsistency = mUsesAccessibilityCache
&& ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & eventType) != 0);
- if (!serviceWantsEvent && !requiredForCacheConsistency) {
+ if (!clientWantsEvent && !requiredForCacheConsistency) {
return;
}
@@ -1774,7 +1787,7 @@
return;
}
// Make a copy since during dispatch it is possible the event to
- // be modified to remove its source if the receiving service does
+ // be modified to remove its source if the receiving client does
// not have permission to access the window content.
AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
Message message;
@@ -1792,22 +1805,20 @@
// Send all messages, bypassing mPendingEvents
message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
}
- message.arg1 = serviceWantsEvent ? 1 : 0;
+ message.arg1 = clientWantsEvent ? 1 : 0;
mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
}
}
/**
- * Determines if given event can be dispatched to a service based on the package of the
- * event source. Specifically, a service is notified if it is interested in events from the
- * package.
+ * Determines if given event can be dispatched to a client based on the package of the event
+ * source. Specifically, a client is notified if it is interested in events from the package.
*
* @param event The event.
- * @return True if the listener should be notified, false otherwise.
+ * @return True if the client should be notified, false otherwise.
*/
- private boolean wantsEventLocked(AccessibilityEvent event) {
-
+ private boolean clientWantsEventLocked(AccessibilityEvent event) {
if (!canReceiveEventsLocked()) {
return false;
}
@@ -1838,22 +1849,20 @@
}
/**
- * Notifies an accessibility service client for a scheduled event given the event type.
+ * Notifies a client for a scheduled event given the event type.
*
* @param eventType The type of the event to dispatch.
*/
private void notifyAccessibilityEventInternal(
- int eventType,
- AccessibilityEvent event,
- boolean serviceWantsEvent) {
- IAccessibilityServiceClient listener;
+ int eventType, AccessibilityEvent event, boolean clientWantsEvent) {
+ IAccessibilityServiceClient client;
synchronized (mLock) {
- listener = mServiceInterface;
+ client = mClient;
- // If the service died/was disabled while the message for dispatching
- // the accessibility event was propagating the listener may be null.
- if (listener == null) {
+ // If the client (in the application process) died/was disabled while the message for
+ // dispatching the accessibility event was propagating, "client" may be null.
+ if (client == null) {
return;
}
@@ -1868,7 +1877,7 @@
// 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
// which posts a message for dispatching an event and stores the event
// in mPendingEvents.
- // 2) The message is pulled from the queue by the handler on the service
+ // 2) The message is pulled from the queue by the handler on the client
// thread and this method is just about to acquire the lock.
// 3) Another binder thread acquires the lock in notifyAccessibilityEvent
// 4) notifyAccessibilityEvent recycles the event that this method was about
@@ -1876,7 +1885,7 @@
// 5) This method grabs the new event, processes it, and removes it from
// mPendingEvents
// 6) The second message dispatched in (4) arrives, but the event has been
- // remvoved in (5).
+ // removed in (5).
event = mPendingEvents.get(eventType);
if (event == null) {
return;
@@ -1893,14 +1902,14 @@
try {
if (svcClientTracingEnabled()) {
- logTraceSvcClient("onAccessibilityEvent", event + ";" + serviceWantsEvent);
+ logTraceSvcClient("onAccessibilityEvent", event + ";" + clientWantsEvent);
}
- listener.onAccessibilityEvent(event, serviceWantsEvent);
+ client.onAccessibilityEvent(event, clientWantsEvent);
if (DEBUG) {
- Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
+ Slog.i(LOG_TAG, "Event " + event + " sent to " + client);
}
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
+ Slog.e(LOG_TAG, "Error during sending " + event + " to " + client, re);
} finally {
event.recycle();
}
@@ -1978,122 +1987,126 @@
return (mGenericMotionEventSources & eventSourceWithoutClass) != 0;
}
-
/**
- * Called by the invocation handler to notify the service that the
- * state of magnification has changed.
+ * Called by the invocation handler to notify the client that the state of magnification has
+ * changed.
*/
- private void notifyMagnificationChangedInternal(int displayId, @NonNull Region region,
- @NonNull MagnificationConfig config) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ private void notifyMagnificationChangedInternal(
+ int displayId, @NonNull Region region, @NonNull MagnificationConfig config) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onMagnificationChanged", displayId + ", " + region + ", "
+ config.toString());
}
- listener.onMagnificationChanged(displayId, region, config);
+ client.onMagnificationChanged(displayId, region, config);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
+ Slog.e(LOG_TAG, "Error sending magnification changes to " + mClientBinder, re);
}
}
}
/**
- * Called by the invocation handler to notify the service that the state of the soft
- * keyboard show mode has changed.
+ * Called by the invocation handler to notify the client that the state of the soft keyboard
+ * show mode has changed.
*/
private void notifySoftKeyboardShowModeChangedInternal(int showState) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onSoftKeyboardShowModeChanged", String.valueOf(showState));
}
- listener.onSoftKeyboardShowModeChanged(showState);
+ client.onSoftKeyboardShowModeChanged(showState);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService,
+ Slog.e(
+ LOG_TAG,
+ "Error sending soft keyboard show mode changes to " + mClientBinder,
re);
}
}
}
private void notifyAccessibilityButtonClickedInternal(int displayId) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onAccessibilityButtonClicked", String.valueOf(displayId));
}
- listener.onAccessibilityButtonClicked(displayId);
+ client.onAccessibilityButtonClicked(displayId);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re);
+ Slog.e(LOG_TAG, "Error sending accessibility button click to " + mClientBinder, re);
}
}
}
private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) {
- // Only notify the service if it's not been notified or the state has changed
+ // Only notify the client if it's not been notified or the state has changed
if (mReceivedAccessibilityButtonCallbackSinceBind
&& (mLastAccessibilityButtonCallbackState == available)) {
return;
}
mReceivedAccessibilityButtonCallbackSinceBind = true;
mLastAccessibilityButtonCallbackState = available;
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onAccessibilityButtonAvailabilityChanged",
String.valueOf(available));
}
- listener.onAccessibilityButtonAvailabilityChanged(available);
+ client.onAccessibilityButtonAvailabilityChanged(available);
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error sending accessibility button availability change to " + mService,
+ Slog.e(
+ LOG_TAG,
+ "Error sending accessibility button availability change to "
+ + mClientBinder,
re);
}
}
}
private void notifyGestureInternal(AccessibilityGestureEvent gestureInfo) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onGesture", gestureInfo.toString());
}
- listener.onGesture(gestureInfo);
+ client.onGesture(gestureInfo);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during sending gesture " + gestureInfo
- + " to " + mService, re);
- }
- }
- }
-
- private void notifySystemActionsChangedInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
- try {
- if (svcClientTracingEnabled()) {
- logTraceSvcClient("onSystemActionsChanged", "");
- }
- listener.onSystemActionsChanged();
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending system actions change to " + mService,
+ Slog.e(
+ LOG_TAG,
+ "Error during sending gesture " + gestureInfo + " to " + mClientBinder,
re);
}
}
}
+ private void notifySystemActionsChangedInternal() {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
+ try {
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("onSystemActionsChanged", "");
+ }
+ client.onSystemActionsChanged();
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending system actions change to " + mClientBinder, re);
+ }
+ }
+ }
+
private void notifyClearAccessibilityCacheInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("clearAccessibilityCache", "");
}
- listener.clearAccessibilityCache();
+ client.clearAccessibilityCache();
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error during requesting accessibility info cache"
+ " to be cleared.", re);
@@ -2106,70 +2119,66 @@
private void setImeSessionEnabledInternal(IAccessibilityInputMethodSession session,
boolean enabled) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null && session != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null && session != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("createImeSession", "");
}
- listener.setImeSessionEnabled(session, enabled);
+ client.setImeSessionEnabled(session, enabled);
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error requesting IME session from " + mService, re);
+ Slog.e(LOG_TAG, "Error requesting IME session from " + mClientBinder, re);
}
}
}
private void bindInputInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("bindInput", "");
}
- listener.bindInput();
+ client.bindInput();
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error binding input to " + mService, re);
+ Slog.e(LOG_TAG, "Error binding input to " + mClientBinder, re);
}
}
}
private void unbindInputInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("unbindInput", "");
}
- listener.unbindInput();
+ client.unbindInput();
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error unbinding input to " + mService, re);
+ Slog.e(LOG_TAG, "Error unbinding input to " + mClientBinder, re);
}
}
}
private void startInputInternal(IRemoteAccessibilityInputConnection connection,
EditorInfo editorInfo, boolean restarting) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("startInput", "editorInfo=" + editorInfo
+ " restarting=" + restarting);
}
- listener.startInput(connection, editorInfo, restarting);
+ client.startInput(connection, editorInfo, restarting);
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error starting input to " + mService, re);
+ Slog.e(LOG_TAG, "Error starting input to " + mClientBinder, re);
}
}
}
- protected IAccessibilityServiceClient getServiceInterfaceSafely() {
+ protected IAccessibilityServiceClient getClientSafely() {
synchronized (mLock) {
- return mServiceInterface;
+ return mClient;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 1145412..d595d02 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1435,8 +1435,8 @@
interfacesToInterrupt = new ArrayList<>(services.size());
for (int i = 0; i < services.size(); i++) {
AccessibilityServiceConnection service = services.get(i);
- IBinder a11yServiceBinder = service.mService;
- IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
+ IBinder a11yServiceBinder = service.mClientBinder;
+ IAccessibilityServiceClient a11yServiceInterface = service.mClient;
if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
interfacesToInterrupt.add(a11yServiceInterface);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 786d167..15999d1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -166,8 +166,9 @@
if (userState.getBindInstantServiceAllowedLocked()) {
flags |= Context.BIND_ALLOW_INSTANT;
}
- if (mService == null && mContext.bindServiceAsUser(
- mIntent, this, flags, new UserHandle(userState.mUserId))) {
+ if (mClientBinder == null
+ && mContext.bindServiceAsUser(
+ mIntent, this, flags, new UserHandle(userState.mUserId))) {
userState.getBindingServicesLocked().add(mComponentName);
}
} finally {
@@ -227,20 +228,20 @@
addWindowTokensForAllDisplays();
}
synchronized (mLock) {
- if (mService != service) {
- if (mService != null) {
- mService.unlinkToDeath(this, 0);
+ if (mClientBinder != service) {
+ if (mClientBinder != null) {
+ mClientBinder.unlinkToDeath(this, 0);
}
- mService = service;
+ mClientBinder = service;
try {
- mService.linkToDeath(this, 0);
+ mClientBinder.linkToDeath(this, 0);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Failed registering death link");
binderDied();
return;
}
}
- mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
+ mClient = IAccessibilityServiceClient.Stub.asInterface(service);
if (userState == null) return;
userState.addServiceLocked(this);
mSystemSupport.onClientChangeLocked(false);
@@ -261,7 +262,7 @@
}
private void initializeService() {
- IAccessibilityServiceClient serviceInterface = null;
+ IAccessibilityServiceClient client = null;
synchronized (mLock) {
AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
@@ -272,18 +273,17 @@
bindingServices.remove(mComponentName);
crashedServices.remove(mComponentName);
mAccessibilityServiceInfo.crashed = false;
- serviceInterface = mServiceInterface;
+ client = mClient;
}
// There's a chance that service is removed from enabled_accessibility_services setting
// key, but skip unbinding because of it's in binding state. Unbinds it if it's
// not in enabled service list.
- if (serviceInterface != null
- && !userState.getEnabledServicesLocked().contains(mComponentName)) {
+ if (client != null && !userState.getEnabledServicesLocked().contains(mComponentName)) {
mSystemSupport.onClientChangeLocked(false);
return;
}
}
- if (serviceInterface == null) {
+ if (client == null) {
binderDied();
return;
}
@@ -292,10 +292,9 @@
logTraceSvcClient("init",
this + "," + mId + "," + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
}
- serviceInterface.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
+ client.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
} catch (RemoteException re) {
- Slog.w(LOG_TAG, "Error while setting connection for service: "
- + serviceInterface, re);
+ Slog.w(LOG_TAG, "Error while setting connection for service: " + client, re);
binderDied();
}
}
@@ -496,7 +495,7 @@
@Override
public boolean isCapturingFingerprintGestures() {
- return (mServiceInterface != null)
+ return (mClient != null)
&& mSecurityPolicy.canCaptureFingerprintGestures(this)
&& mCaptureFingerprintGestures;
}
@@ -506,17 +505,17 @@
if (!isCapturingFingerprintGestures()) {
return;
}
- IAccessibilityServiceClient serviceInterface;
+ IAccessibilityServiceClient client;
synchronized (mLock) {
- serviceInterface = mServiceInterface;
+ client = mClient;
}
- if (serviceInterface != null) {
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient(
"onFingerprintCapturingGesturesChanged", String.valueOf(active));
}
- mServiceInterface.onFingerprintCapturingGesturesChanged(active);
+ mClient.onFingerprintCapturingGesturesChanged(active);
} catch (RemoteException e) {
}
}
@@ -527,16 +526,16 @@
if (!isCapturingFingerprintGestures()) {
return;
}
- IAccessibilityServiceClient serviceInterface;
+ IAccessibilityServiceClient client;
synchronized (mLock) {
- serviceInterface = mServiceInterface;
+ client = mClient;
}
- if (serviceInterface != null) {
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onFingerprintGesture", String.valueOf(gesture));
}
- mServiceInterface.onFingerprintGesture(gesture);
+ mClient.onFingerprintGesture(gesture);
} catch (RemoteException e) {
}
}
@@ -546,7 +545,7 @@
@Override
public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
synchronized (mLock) {
- if (mServiceInterface != null && mSecurityPolicy.canPerformGestures(this)) {
+ if (mClient != null && mSecurityPolicy.canPerformGestures(this)) {
final long identity = Binder.clearCallingIdentity();
try {
MotionEventInjector motionEventInjector =
@@ -557,16 +556,18 @@
if (motionEventInjector != null
&& mWindowManagerService.isTouchOrFaketouchDevice()) {
motionEventInjector.injectEvents(
- gestureSteps.getList(), mServiceInterface, sequence, displayId);
+ gestureSteps.getList(), mClient, sequence, displayId);
} else {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onPerformGestureResult", sequence + ", false");
}
- mServiceInterface.onPerformGestureResult(sequence, false);
+ mClient.onPerformGestureResult(sequence, false);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending motion event injection failure to "
- + mServiceInterface, re);
+ Slog.e(
+ LOG_TAG,
+ "Error sending motion event injection failure to " + mClient,
+ re);
}
}
} finally {
@@ -631,48 +632,47 @@
@Override
protected void createImeSessionInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("createImeSession", "");
}
AccessibilityInputMethodSessionCallback
callback = new AccessibilityInputMethodSessionCallback(mUserId);
- listener.createImeSession(callback);
+ client.createImeSession(callback);
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error requesting IME session from " + mService, re);
+ Slog.e(LOG_TAG, "Error requesting IME session from " + mClientBinder, re);
}
}
}
private void notifyMotionEventInternal(MotionEvent event) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (mTrace.isA11yTracingEnabled()) {
logTraceSvcClient(".onMotionEvent ",
event.toString());
}
- listener.onMotionEvent(event);
+ client.onMotionEvent(event);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending motion event to" + mService, re);
+ Slog.e(LOG_TAG, "Error sending motion event to" + mClientBinder, re);
}
}
}
private void notifyTouchStateInternal(int displayId, int state) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (mTrace.isA11yTracingEnabled()) {
logTraceSvcClient(".onTouchStateChanged ",
TouchInteractionController.stateToString(state));
}
- listener.onTouchStateChanged(displayId, state);
+ client.onTouchStateChanged(displayId, state);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending motion event to" + mService, re);
+ Slog.e(LOG_TAG, "Error sending motion event to" + mClientBinder, re);
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 4cb3d24..cd97d83 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -109,14 +109,11 @@
return mDeviceId;
}
- /**
- * Called when the proxy is registered.
- */
- void initializeServiceInterface(IAccessibilityServiceClient serviceInterface)
- throws RemoteException {
- mServiceInterface = serviceInterface;
- mService = serviceInterface.asBinder();
- mServiceInterface.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
+ /** Called when the proxy is registered. */
+ void initializeClient(IAccessibilityServiceClient client) throws RemoteException {
+ mClient = client;
+ mClientBinder = client.asBinder();
+ mClient.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index b4deeb0..da11a76 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -214,7 +214,7 @@
mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
}
});
- connection.initializeServiceInterface(client);
+ connection.initializeClient(client);
}
private void registerVirtualDeviceListener() {
@@ -561,8 +561,8 @@
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null && proxy.getDeviceId() == deviceId) {
- final IBinder proxyBinder = proxy.mService;
- final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
+ final IBinder proxyBinder = proxy.mClientBinder;
+ final IAccessibilityServiceClient proxyInterface = proxy.mClient;
if ((proxyBinder != null) && (proxyInterface != null)) {
interfaces.add(proxyInterface);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index f85d786..ed4eeb5 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -107,8 +107,7 @@
Binder.getCallingUserHandle().getIdentifier());
if (mUiAutomationService != null) {
throw new IllegalStateException(
- "UiAutomationService " + mUiAutomationService.mServiceInterface
- + "already registered!");
+ "UiAutomationService " + mUiAutomationService.mClient + "already registered!");
}
try {
@@ -130,10 +129,9 @@
mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal,
systemActionPerformer, awm);
mUiAutomationServiceOwner = owner;
- mUiAutomationService.mServiceInterface = serviceClient;
+ mUiAutomationService.mClient = serviceClient;
try {
- mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService,
- 0);
+ mUiAutomationService.mClient.asBinder().linkToDeath(mUiAutomationService, 0);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Failed registering death link: " + re);
destroyUiAutomationService();
@@ -149,10 +147,10 @@
synchronized (mLock) {
if (useAccessibility()
&& ((mUiAutomationService == null)
- || (serviceClient == null)
- || (mUiAutomationService.mServiceInterface == null)
- || (serviceClient.asBinder()
- != mUiAutomationService.mServiceInterface.asBinder()))) {
+ || (serviceClient == null)
+ || (mUiAutomationService.mClient == null)
+ || (serviceClient.asBinder()
+ != mUiAutomationService.mClient.asBinder()))) {
throw new IllegalStateException("UiAutomationService " + serviceClient
+ " not registered!");
}
@@ -230,8 +228,7 @@
private void destroyUiAutomationService() {
synchronized (mLock) {
if (mUiAutomationService != null) {
- mUiAutomationService.mServiceInterface.asBinder().unlinkToDeath(
- mUiAutomationService, 0);
+ mUiAutomationService.mClient.asBinder().unlinkToDeath(mUiAutomationService, 0);
mUiAutomationService.onRemoved();
mUiAutomationService.resetLocked();
mUiAutomationService = null;
@@ -271,40 +268,48 @@
void connectServiceUnknownThread() {
// This needs to be done on the main thread
- mMainHandler.post(() -> {
- try {
- final IAccessibilityServiceClient serviceInterface;
- final UiAutomationService uiAutomationService;
- synchronized (mLock) {
- serviceInterface = mServiceInterface;
- uiAutomationService = mUiAutomationService;
- if (serviceInterface == null) {
- mService = null;
- } else {
- mService = mServiceInterface.asBinder();
- mService.linkToDeath(this, 0);
+ mMainHandler.post(
+ () -> {
+ try {
+ final IAccessibilityServiceClient client;
+ final UiAutomationService uiAutomationService;
+ synchronized (mLock) {
+ client = mClient;
+ uiAutomationService = mUiAutomationService;
+ if (client == null) {
+ mClientBinder = null;
+ } else {
+ mClientBinder = mClient.asBinder();
+ mClientBinder.linkToDeath(this, 0);
+ }
+ }
+ // If the client is null, the UiAutomation has been shut down on
+ // another thread.
+ if (client != null && uiAutomationService != null) {
+ uiAutomationService.addWindowTokensForAllDisplays();
+ if (mTrace.isA11yTracingEnabledForTypes(
+ AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+ mTrace.logTrace(
+ "UiAutomationService.connectServiceUnknownThread",
+ AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT,
+ "serviceConnection="
+ + this
+ + ";connectionId="
+ + mId
+ + "windowToken="
+ + mOverlayWindowTokens.get(
+ Display.DEFAULT_DISPLAY));
+ }
+ client.init(
+ this,
+ mId,
+ mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
+ }
+ } catch (RemoteException re) {
+ Slog.w(LOG_TAG, "Error initializing connection", re);
+ destroyUiAutomationService();
}
- }
- // If the serviceInterface is null, the UiAutomation has been shut down on
- // another thread.
- if (serviceInterface != null && uiAutomationService != null) {
- uiAutomationService.addWindowTokensForAllDisplays();
- if (mTrace.isA11yTracingEnabledForTypes(
- AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
- mTrace.logTrace("UiAutomationService.connectServiceUnknownThread",
- AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT,
- "serviceConnection=" + this + ";connectionId=" + mId
- + "windowToken="
- + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
- }
- serviceInterface.init(this, mId,
- mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
- }
- } catch (RemoteException re) {
- Slog.w(LOG_TAG, "Error initializing connection", re);
- destroyUiAutomationService();
- }
- });
+ });
}
@Override
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index b2c679f..f6ac706 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -1444,7 +1444,7 @@
* in {@link #allocateAppWidgetId}.
*
* @param callingPackage The package that calls this method.
- * @param appWidgetId The id of theapp widget to bind.
+ * @param appWidgetId The id of the widget to bind.
* @param providerProfileId The user/profile id of the provider.
* @param providerComponent The {@link ComponentName} that provides the widget.
* @param options The options to pass to the provider.
@@ -1738,6 +1738,14 @@
return false;
}
+ /**
+ * Called by a {@link AppWidgetHost} to remove all records (i.e. {@link Host}
+ * and all {@link Widget} associated with the host) from a specified host.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param hostId id of the {@link Host}.
+ * @see AppWidgetHost#deleteHost()
+ */
@Override
public void deleteHost(String callingPackage, int hostId) {
final int userId = UserHandle.getCallingUserId();
@@ -1771,6 +1779,15 @@
}
}
+ /**
+ * Called by a host process to remove all records (i.e. {@link Host}
+ * and all {@link Widget} associated with the host) from all hosts associated
+ * with the calling process.
+ *
+ * Typically used in clean up after test execution.
+ *
+ * @see AppWidgetHost#deleteAllHosts()
+ */
@Override
public void deleteAllHosts() {
final int userId = UserHandle.getCallingUserId();
@@ -1805,6 +1822,18 @@
}
}
+ /**
+ * Returns the {@link AppWidgetProviderInfo} for the specified AppWidget.
+ *
+ * Typically used by launcher during the restore of an AppWidget, the binding
+ * of new AppWidget, and during grid size migration.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetId Id of the widget.
+ * @return The {@link AppWidgetProviderInfo} for the specified widget.
+ *
+ * @see AppWidgetManager#getAppWidgetInfo(int)
+ */
@Override
public AppWidgetProviderInfo getAppWidgetInfo(String callingPackage, int appWidgetId) {
final int userId = UserHandle.getCallingUserId();
@@ -1859,6 +1888,17 @@
}
}
+ /**
+ * Returns the most recent {@link RemoteViews} of the specified AppWidget.
+ * Typically serves as a cache of the content of the AppWidget.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetId Id of the widget.
+ * @return The {@link RemoteViews} of the specified widget.
+ *
+ * @see AppWidgetHost#updateAppWidgetDeferred(String, int)
+ * @see AppWidgetHost#setListener(int, AppWidgetHostListener)
+ */
@Override
public RemoteViews getAppWidgetViews(String callingPackage, int appWidgetId) {
final int userId = UserHandle.getCallingUserId();
@@ -1886,6 +1926,29 @@
}
}
+ /**
+ * Update the extras for a given widget instance.
+ * <p>
+ * The extras can be used to embed additional information about this widget to be accessed
+ * by the associated widget's AppWidgetProvider.
+ *
+ * <p>
+ * The new options are merged into existing options using {@link Bundle#putAll} semantics.
+ *
+ * <p>
+ * Typically called by a {@link AppWidgetHost} (e.g. Launcher) to notify
+ * {@link AppWidgetProvider} regarding contextual changes (e.g. sizes) when rendering the
+ * widget.
+ * Calling this method would trigger onAppWidgetOptionsChanged() callback on the provider's
+ * side.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetId Id of the widget.
+ * @param options New options associate with this widget.
+ *
+ * @see AppWidgetManager#getAppWidgetOptions(int, Bundle)
+ * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
+ */
@Override
public void updateAppWidgetOptions(String callingPackage, int appWidgetId, Bundle options) {
final int userId = UserHandle.getCallingUserId();
@@ -1919,6 +1982,21 @@
}
}
+ /**
+ * Get the extras associated with a given widget instance.
+ * <p>
+ * The extras can be used to embed additional information about this widget to be accessed
+ * by the associated widget's AppWidgetProvider.
+ *
+ * Typically called by a host process (e.g. Launcher) to determine if they need to update the
+ * options of the widget.
+ *
+ * @see #updateAppWidgetOptions(String, int, Bundle)
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetId Id of the widget.
+ * @return The options associated with the specified widget instance.
+ */
@Override
public Bundle getAppWidgetOptions(String callingPackage, int appWidgetId) {
final int userId = UserHandle.getCallingUserId();
@@ -1946,6 +2024,28 @@
}
}
+ /**
+ * Updates the content of the widgets (as specified by appWidgetIds) using the provided
+ * {@link RemoteViews}.
+ *
+ * Typically called by the provider's process. Either in response to the invocation of
+ * {@link AppWidgetProvider#onUpdate} or upon receiving the
+ * {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} broadcast.
+ *
+ * <p>
+ * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should
+ * contain a complete representation of the widget. For performing partial widget updates, see
+ * {@link #partiallyUpdateAppWidgetIds(String, int[], RemoteViews)}.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetIds Ids of the widgets to be updated.
+ * @param views The RemoteViews object containing the update.
+ *
+ * @see AppWidgetProvider#onUpdate(Context, AppWidgetManager, int[])
+ * @see AppWidgetManager#ACTION_APPWIDGET_UPDATE
+ * @see AppWidgetManager#updateAppWidget(int, RemoteViews)
+ * @see AppWidgetManager#updateAppWidget(int[], RemoteViews)
+ */
@Override
public void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
RemoteViews views) {
@@ -1956,6 +2056,27 @@
updateAppWidgetIds(callingPackage, appWidgetIds, views, false);
}
+ /**
+ * Perform an incremental update or command on the widget(s) specified by appWidgetIds.
+ * <p>
+ * This update differs from {@link #updateAppWidgetIds(int[], RemoteViews)} in that the
+ * RemoteViews object which is passed is understood to be an incomplete representation of the
+ * widget, and hence does not replace the cached representation of the widget. As of API
+ * level 17, the new properties set within the views objects will be appended to the cached
+ * representation of the widget, and hence will persist.
+ *
+ * <p>
+ * This method will be ignored if a widget has not received a full update via
+ * {@link #updateAppWidget(int[], RemoteViews)}.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetIds Ids of the widgets to be updated.
+ * @param views The RemoteViews object containing the incremental update / command.
+ *
+ * @see AppWidgetManager#partiallyUpdateAppWidget(int[], RemoteViews)
+ * @see RemoteViews#setDisplayedChild(int, int)
+ * @see RemoteViews#setScrollPosition(int, int)
+ */
@Override
public void partiallyUpdateAppWidgetIds(String callingPackage, int[] appWidgetIds,
RemoteViews views) {
@@ -1966,6 +2087,24 @@
updateAppWidgetIds(callingPackage, appWidgetIds, views, true);
}
+ /**
+ * Callback function which marks specified providers as extended from AppWidgetProvider.
+ *
+ * This information is used to determine if the system can combine
+ * {@link AppWidgetManager#ACTION_APPWIDGET_ENABLED} and
+ * {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} into a single broadcast.
+ *
+ * Note: The system can only combine the two broadcasts if the provider is extended from
+ * AppWidgetProvider. When they do, they are expected to override the
+ * {@link AppWidgetProvider#onUpdate} callback function to provide updates, as opposed to
+ * listening for {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} broadcasts directly.
+ *
+ * @see AppWidgetManager#ACTION_APPWIDGET_ENABLED
+ * @see AppWidgetManager#ACTION_APPWIDGET_UPDATE
+ * @see AppWidgetManager#ACTION_APPWIDGET_ENABLE_AND_UPDATE
+ * @see AppWidgetProvider#onReceive(Context, Intent)
+ * @see #sendEnableAndUpdateIntentLocked
+ */
@Override
public void notifyProviderInheritance(@Nullable final ComponentName[] componentNames) {
final int userId = UserHandle.getCallingUserId();
@@ -2000,6 +2139,15 @@
}
}
+ /**
+ * Notifies the specified collection view in all the specified AppWidget instances
+ * to invalidate their data.
+ *
+ * This method is effectively deprecated since
+ * {@link RemoteViews#setRemoteAdapter(int, Intent)} has been deprecated.
+ *
+ * @see AppWidgetManager#notifyAppWidgetViewDataChanged(int[], int)
+ */
@Override
public void notifyAppWidgetViewDataChanged(String callingPackage, int[] appWidgetIds,
int viewId) {
@@ -2035,6 +2183,18 @@
}
}
+ /**
+ * Updates the content of all widgets associated with given provider (as specified by
+ * componentName) using the provided {@link RemoteViews}.
+ *
+ * Typically called by the provider's process when there's an update that needs to be supplied
+ * to all instances of the widgets.
+ *
+ * @param componentName The component name of the provider.
+ * @param views The RemoteViews object containing the update.
+ *
+ * @see AppWidgetManager#updateAppWidget(ComponentName, RemoteViews)
+ */
@Override
public void updateAppWidgetProvider(ComponentName componentName, RemoteViews views) {
final int userId = UserHandle.getCallingUserId();
@@ -2068,6 +2228,27 @@
}
}
+ /**
+ * Updates the info for the supplied AppWidget provider. Apps can use this to change the default
+ * behavior of the widget based on the state of the app (e.g., if the user is logged in
+ * or not). Calling this API completely replaces the previous definition.
+ *
+ * <p>
+ * The manifest entry of the provider should contain an additional meta-data tag similar to
+ * {@link AppWidgetManager#META_DATA_APPWIDGET_PROVIDER} which should point to any alternative
+ * definitions for the provider.
+ *
+ * <p>
+ * This is persisted across device reboots and app updates. If this meta-data key is not
+ * present in the manifest entry, the info reverts to default.
+ *
+ * @param provider {@link ComponentName} for the {@link
+ * android.content.BroadcastReceiver BroadcastReceiver} provider for your AppWidget.
+ * @param metaDataKey key for the meta-data tag pointing to the new provider info. Use null
+ * to reset any previously set info.
+ *
+ * @see AppWidgetManager#updateAppWidgetProviderInfo(ComponentName, String)
+ */
@Override
public void updateAppWidgetProviderInfo(ComponentName componentName, String metadataKey) {
final int userId = UserHandle.getCallingUserId();
@@ -2119,6 +2300,11 @@
}
}
+ /**
+ * Returns true if the default launcher app on the device (the one that currently
+ * holds the android.app.role.HOME role) can support pinning widgets
+ * (typically means adding widgets into home screen).
+ */
@Override
public boolean isRequestPinAppWidgetSupported() {
synchronized (mLock) {
@@ -2133,6 +2319,44 @@
LauncherApps.PinItemRequest.REQUEST_TYPE_APPWIDGET);
}
+ /**
+ * Request to pin an app widget on the current launcher. It's up to the launcher to accept this
+ * request (optionally showing a user confirmation). If the request is accepted, the caller will
+ * get a confirmation with extra {@link #EXTRA_APPWIDGET_ID}.
+ *
+ * <p>When a request is denied by the user, the caller app will not get any response.
+ *
+ * <p>Only apps with a foreground activity or a foreground service can call it. Otherwise
+ * it'll throw {@link IllegalStateException}.
+ *
+ * <p>It's up to the launcher how to handle previous pending requests when the same package
+ * calls this API multiple times in a row. It may ignore the previous requests,
+ * for example.
+ *
+ * <p>Launcher will not show the configuration activity associated with the provider in this
+ * case. The app could either show the configuration activity as a response to the callback,
+ * or show if before calling the API (various configurations can be encapsulated in
+ * {@code successCallback} to avoid persisting them before the widgetId is known).
+ *
+ * @param provider The {@link ComponentName} for the {@link
+ * android.content.BroadcastReceiver BroadcastReceiver} provider for your AppWidget.
+ * @param extras If not null, this is passed to the launcher app. For eg {@link
+ * #EXTRA_APPWIDGET_PREVIEW} can be used for a custom preview.
+ * @param successCallback If not null, this intent will be sent when the widget is created.
+ *
+ * @return {@code TRUE} if the launcher supports this feature. Note the API will return without
+ * waiting for the user to respond, so getting {@code TRUE} from this API does *not* mean
+ * the shortcut is pinned. {@code FALSE} if the launcher doesn't support this feature or if
+ * calling app belongs to a user-profile with items restricted on home screen.
+ *
+ * @see android.content.pm.ShortcutManager#isRequestPinShortcutSupported()
+ * @see android.content.pm.ShortcutManager#requestPinShortcut(ShortcutInfo, IntentSender)
+ * @see AppWidgetManager#isRequestPinAppWidgetSupported()
+ * @see AppWidgetManager#requestPinAppWidget(ComponentName, Bundle, PendingIntent)
+ *
+ * @throws IllegalStateException The caller doesn't have a foreground activity or a foreground
+ * service or when the user is locked.
+ */
@Override
public boolean requestPinAppWidget(String callingPackage, ComponentName componentName,
Bundle extras, IntentSender resultSender) {
@@ -2184,6 +2408,24 @@
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
}
+ /**
+ * Gets the AppWidget providers for the given user profile. User profile can only
+ * be the current user or a profile of the current user. For example, the current
+ * user may have a corporate profile. In this case the parent user profile has a
+ * child profile, the corporate one.
+ *
+ * @param categoryFilter Will only return providers which register as any of the specified
+ * specified categories. See {@link AppWidgetProviderInfo#widgetCategory}.
+ * @param profile A profile of the current user which to be queried. The user
+ * is itself also a profile. If null, the providers only for the current user
+ * are returned.
+ * @param packageName If specified, will only return providers from the given package.
+ * @return The installed providers.
+ *
+ * @see android.os.Process#myUserHandle()
+ * @see android.os.UserManager#getUserProfiles()
+ * @see AppWidgetManager#getInstalledProvidersForProfile(int, UserHandle, String)
+ */
@Override
public ParceledListSlice<AppWidgetProviderInfo> getInstalledProvidersForProfile(int categoryFilter,
int profileId, String packageName) {
@@ -2244,6 +2486,26 @@
}
}
+ /**
+ * Updates the content of the widgets (as specified by appWidgetIds) using the provided
+ * {@link RemoteViews}.
+ *
+ * If performing a partial update, the given RemoteViews object is merged into existing
+ * RemoteViews object.
+ *
+ * Fails silently if appWidgetIds is null or empty, or cannot found a widget with the given
+ * appWidgetId.
+ *
+ * @param callingPackage The package that calls this method.
+ * @param appWidgetIds Ids of the widgets to be updated.
+ * @param views The RemoteViews object containing the update.
+ * @param partially Whether it was a partial update.
+ *
+ * @see AppWidgetProvider#onUpdate(Context, AppWidgetManager, int[])
+ * @see AppWidgetManager#ACTION_APPWIDGET_UPDATE
+ * @see AppWidgetManager#updateAppWidget(int, RemoteViews)
+ * @see AppWidgetManager#updateAppWidget(int[], RemoteViews)
+ */
private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
RemoteViews views, boolean partially) {
final int userId = UserHandle.getCallingUserId();
@@ -2273,12 +2535,29 @@
}
}
+ /**
+ * Increment the counter of widget ids and return the new id.
+ *
+ * Typically called by {@link #allocateAppWidgetId} when a instance of widget is created,
+ * either as a result of being pinned by launcher or added during a restore.
+ *
+ * Note: A widget id is a monotonically increasing integer that uniquely identifies the widget
+ * instance.
+ *
+ * TODO: Revisit this method and determine whether we need to alter the widget id during
+ * the restore since widget id mismatch potentially leads to some issues in the past.
+ */
private int incrementAndGetAppWidgetIdLocked(int userId) {
final int appWidgetId = peekNextAppWidgetIdLocked(userId) + 1;
mNextAppWidgetIds.put(userId, appWidgetId);
return appWidgetId;
}
+ /**
+ * Called by {@link #readProfileStateFromFileLocked} when widgets/providers/hosts are loaded
+ * from disk, which ensures mNextAppWidgetIds is larger than any existing widget id for given
+ * user.
+ */
private void setMinAppWidgetIdLocked(int userId, int minWidgetId) {
final int nextAppWidgetId = peekNextAppWidgetIdLocked(userId);
if (nextAppWidgetId < minWidgetId) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 88edb12..3499a3a 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1773,8 +1773,7 @@
// Create a Session for the target user and pass in the bundle
completeCloningAccount(response, result, account, toAccounts, userFrom);
} else {
- // Bundle format is not defined.
- super.onResultSkipSanitization(result);
+ super.onResult(result);
}
}
}.bind();
@@ -1861,8 +1860,7 @@
// account to avoid retries?
// TODO: what we do with the visibility?
- // Bundle format is not defined.
- super.onResultSkipSanitization(result);
+ super.onResult(result);
}
@Override
@@ -2108,7 +2106,6 @@
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
IAccountManagerResponse response = getResponseAndClose();
if (response != null) {
try {
@@ -2462,7 +2459,6 @@
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
&& !result.containsKey(AccountManager.KEY_INTENT)) {
final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
@@ -2977,7 +2973,6 @@
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
if (result != null) {
String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL);
Bundle bundle = new Bundle();
@@ -3155,7 +3150,6 @@
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
if (result != null) {
if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) {
Intent intent = newGrantCredentialsPermissionIntent(
@@ -3627,12 +3621,6 @@
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- Bundle sessionBundle = null;
- if (result != null) {
- // Session bundle will be removed from result.
- sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
- }
- result = sanitizeBundle(result);
mNumResults++;
Intent intent = null;
if (result != null) {
@@ -3694,6 +3682,7 @@
// bundle contains data necessary for finishing the session
// later. The session bundle will be encrypted here and
// decrypted later when trying to finish the session.
+ Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
if (sessionBundle != null) {
String accountType = sessionBundle.getString(AccountManager.KEY_ACCOUNT_TYPE);
if (TextUtils.isEmpty(accountType)
@@ -4081,7 +4070,6 @@
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
IAccountManagerResponse response = getResponseAndClose();
if (response == null) {
return;
@@ -4395,7 +4383,6 @@
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
mNumResults++;
if (result == null) {
onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
@@ -4952,68 +4939,6 @@
callback, resultReceiver);
}
-
- // All keys for Strings passed from AbstractAccountAuthenticator using Bundle.
- private static final String[] sStringBundleKeys = new String[] {
- AccountManager.KEY_ACCOUNT_NAME,
- AccountManager.KEY_ACCOUNT_TYPE,
- AccountManager.KEY_AUTHTOKEN,
- AccountManager.KEY_AUTH_TOKEN_LABEL,
- AccountManager.KEY_ERROR_MESSAGE,
- AccountManager.KEY_PASSWORD,
- AccountManager.KEY_ACCOUNT_STATUS_TOKEN};
-
- /**
- * Keep only documented fields in a Bundle received from AbstractAccountAuthenticator.
- */
- protected static Bundle sanitizeBundle(Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- Bundle sanitizedBundle = new Bundle();
- Bundle.setDefusable(sanitizedBundle, true);
- int updatedKeysCount = 0;
- for (String stringKey : sStringBundleKeys) {
- if (bundle.containsKey(stringKey)) {
- String value = bundle.getString(stringKey);
- sanitizedBundle.putString(stringKey, value);
- updatedKeysCount++;
- }
- }
- String key = AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY;
- if (bundle.containsKey(key)) {
- long expiryMillis = bundle.getLong(key, 0L);
- sanitizedBundle.putLong(key, expiryMillis);
- updatedKeysCount++;
- }
- key = AccountManager.KEY_BOOLEAN_RESULT;
- if (bundle.containsKey(key)) {
- boolean booleanResult = bundle.getBoolean(key, false);
- sanitizedBundle.putBoolean(key, booleanResult);
- updatedKeysCount++;
- }
- key = AccountManager.KEY_ERROR_CODE;
- if (bundle.containsKey(key)) {
- int errorCode = bundle.getInt(key, 0);
- sanitizedBundle.putInt(key, errorCode);
- updatedKeysCount++;
- }
- key = AccountManager.KEY_INTENT;
- if (bundle.containsKey(key)) {
- Intent intent = bundle.getParcelable(key, Intent.class);
- sanitizedBundle.putParcelable(key, intent);
- updatedKeysCount++;
- }
- if (bundle.containsKey(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE)) {
- // The field is not copied in sanitized bundle.
- updatedKeysCount++;
- }
- if (updatedKeysCount != bundle.size()) {
- Log.w(TAG, "Size mismatch after sanitizeBundle call.");
- }
- return sanitizedBundle;
- }
-
private abstract class Session extends IAccountAuthenticatorResponse.Stub
implements IBinder.DeathRecipient, ServiceConnection {
private final Object mSessionLock = new Object();
@@ -5304,15 +5229,10 @@
}
}
}
+
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
- onResultSkipSanitization(result);
- }
-
- public void onResultSkipSanitization(Bundle result) {
- Bundle.setDefusable(result, true);
mNumResults++;
Intent intent = null;
if (result != null) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b6a4481..4e89b85 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4152,8 +4152,10 @@
}
private void checkAllUsersAreAffiliatedWithDevice() {
- Preconditions.checkCallAuthorization(areAllUsersAffiliatedWithDeviceLocked(),
- "operation not allowed when device has unaffiliated users");
+ synchronized (getLockObject()) {
+ Preconditions.checkCallAuthorization(areAllUsersAffiliatedWithDeviceLocked(),
+ "operation not allowed when device has unaffiliated users");
+ }
}
@Override
@@ -11362,7 +11364,7 @@
if (mOwners.hasDeviceOwner()) {
return false;
}
-
+
final ComponentName profileOwner = getProfileOwnerAsUser(userId);
if (profileOwner == null) {
return false;
@@ -11371,7 +11373,7 @@
if (isManagedProfile(userId)) {
return false;
}
-
+
return true;
}
private void enforceCanQueryLockTaskLocked(ComponentName who, String callerPackageName) {
@@ -18213,6 +18215,7 @@
return false;
}
+ @GuardedBy("getLockObject()")
private boolean areAllUsersAffiliatedWithDeviceLocked() {
return mInjector.binderWithCleanCallingIdentity(() -> {
final List<UserInfo> userInfos = mUserManager.getAliveUsers();
@@ -18310,10 +18313,12 @@
final CallerIdentity caller = getCallerIdentity(admin, packageName);
if (isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
- || areAllUsersAffiliatedWithDeviceLocked());
- enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(),
- UserHandle.USER_ALL);
+ synchronized (getLockObject()) {
+ Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
+ || areAllUsersAffiliatedWithDeviceLocked());
+ enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(),
+ UserHandle.USER_ALL);
+ }
} else {
if (admin != null) {
Preconditions.checkCallAuthorization(
@@ -18325,8 +18330,10 @@
isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
}
- Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
- || areAllUsersAffiliatedWithDeviceLocked());
+ synchronized (getLockObject()) {
+ Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
+ || areAllUsersAffiliatedWithDeviceLocked());
+ }
}
DevicePolicyEventLogger
@@ -24540,7 +24547,8 @@
}
});
}
-
+
+ @GuardedBy("getLockObject()")
private void migrateUserControlDisabledPackagesLocked() {
Binder.withCleanCallingIdentity(() -> {
List<UserInfo> users = mUserManager.getUsers();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 6e6d5a8..8dfd54f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -174,8 +174,8 @@
@Mock private AccessibilityTrace mMockA11yTrace;
@Mock private WindowManagerInternal mMockWindowManagerInternal;
@Mock private SystemActionPerformer mMockSystemActionPerformer;
- @Mock private IBinder mMockService;
- @Mock private IAccessibilityServiceClient mMockServiceInterface;
+ @Mock private IBinder mMockClientBinder;
+ @Mock private IAccessibilityServiceClient mMockClient;
@Mock private KeyEventDispatcher mMockKeyEventDispatcher;
@Mock private IAccessibilityInteractionConnection mMockIA11yInteractionConnection;
@Mock private IAccessibilityInteractionConnectionCallback mMockCallback;
@@ -247,9 +247,9 @@
mSpyServiceInfo, SERVICE_ID, mHandler, new Object(), mMockSecurityPolicy,
mMockSystemSupport, mMockA11yTrace, mMockWindowManagerInternal,
mMockSystemActionPerformer, mMockA11yWindowManager);
- // Assume that the service is connected
- mServiceConnection.mService = mMockService;
- mServiceConnection.mServiceInterface = mMockServiceInterface;
+ // Assume that the client is connected
+ mServiceConnection.mClientBinder = mMockClientBinder;
+ mServiceConnection.mClient = mMockClient;
// Update security policy for this service
when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(true);
@@ -273,7 +273,7 @@
final KeyEvent mockKeyEvent = mock(KeyEvent.class);
mServiceConnection.onKeyEvent(mockKeyEvent, sequenceNumber);
- verify(mMockServiceInterface).onKeyEvent(mockKeyEvent, sequenceNumber);
+ verify(mMockClient).onKeyEvent(mockKeyEvent, sequenceNumber);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index b9ce8ad..0c92abc 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -1163,6 +1163,16 @@
verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
Bundle result = mBundleCaptor.getValue();
+ Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
+ assertNotNull(sessionBundle);
+ // Assert that session bundle is decrypted and hence data is visible.
+ assertEquals(AccountManagerServiceTestFixtures.SESSION_DATA_VALUE_1,
+ sessionBundle.getString(AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1));
+ // Assert finishSessionAsUser added calling uid and pid into the sessionBundle
+ assertTrue(sessionBundle.containsKey(AccountManager.KEY_CALLER_UID));
+ assertTrue(sessionBundle.containsKey(AccountManager.KEY_CALLER_PID));
+ assertEquals(sessionBundle.getString(
+ AccountManager.KEY_ANDROID_PACKAGE_NAME), "APCT.package");
// Verify response data
assertNull(result.getString(AccountManager.KEY_AUTHTOKEN, null));
@@ -2111,6 +2121,12 @@
result.getString(AccountManager.KEY_ACCOUNT_NAME));
assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+
+ Bundle optionBundle = result.getParcelable(
+ AccountManagerServiceTestFixtures.KEY_OPTIONS_BUNDLE);
+ // Assert addAccountAsUser added calling uid and pid into the option bundle
+ assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_UID));
+ assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_PID));
}
@SmallTest
@@ -3441,52 +3457,6 @@
+ (readTotalTime.doubleValue() / readerCount / loopSize));
}
- @SmallTest
- public void testSanitizeBundle_expectedFields() throws Exception {
- Bundle bundle = new Bundle();
- bundle.putString(AccountManager.KEY_ACCOUNT_NAME, "name");
- bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, "type");
- bundle.putString(AccountManager.KEY_AUTHTOKEN, "token");
- bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, "label");
- bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "error message");
- bundle.putString(AccountManager.KEY_PASSWORD, "password");
- bundle.putString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN, "status");
-
- bundle.putLong(AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, 123L);
- bundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
- bundle.putInt(AccountManager.KEY_ERROR_CODE, 456);
-
- Bundle sanitizedBundle = AccountManagerService.sanitizeBundle(bundle);
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_NAME), "name");
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_TYPE), "type");
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_AUTHTOKEN), "token");
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_AUTH_TOKEN_LABEL), "label");
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_ERROR_MESSAGE), "error message");
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_PASSWORD), "password");
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN), "status");
-
- assertEquals(sanitizedBundle.getLong(
- AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, 0), 123L);
- assertEquals(sanitizedBundle.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false), true);
- assertEquals(sanitizedBundle.getInt(AccountManager.KEY_ERROR_CODE, 0), 456);
- }
-
- @SmallTest
- public void testSanitizeBundle_filtersUnexpectedFields() throws Exception {
- Bundle bundle = new Bundle();
- bundle.putString(AccountManager.KEY_ACCOUNT_NAME, "name");
- bundle.putString("unknown_key", "value");
- Bundle sessionBundle = new Bundle();
- bundle.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
-
- Bundle sanitizedBundle = AccountManagerService.sanitizeBundle(bundle);
-
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_NAME), "name");
- assertFalse(sanitizedBundle.containsKey("unknown_key"));
- // It is a valid response from Authenticator which will be accessed using original Bundle
- assertFalse(sanitizedBundle.containsKey(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
- }
-
private void waitForCyclicBarrier(CyclicBarrier cyclicBarrier) {
try {
cyclicBarrier.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 446fdd4..0d261ab 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -243,6 +243,12 @@
// Exported symbols
std::vector<SourcedResourceName> exported_symbols;
+
+ // Flag status
+ FlagStatus flag_status = FlagStatus::NoFlag;
+
+ // Flag
+ std::optional<FeatureFlagAttribute> flag;
};
/**
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 773edc3..fce6aa7 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -546,15 +546,25 @@
{"symbol", std::mem_fn(&ResourceParser::ParseSymbol)},
});
- std::string resource_type = parser->element_name();
- out_resource->flag = GetFlag(parser);
- std::string error;
- auto flag_status = GetFlagStatus(out_resource->flag, options_.feature_flag_values, &error);
- if (flag_status) {
- out_resource->flag_status = flag_status.value();
- } else {
- diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << error);
- return false;
+ std::string_view resource_type = parser->element_name();
+ if (auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"))) {
+ if (options_.flag) {
+ diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+ << "Resource flag are not allowed both in the path and in the file");
+ return false;
+ }
+ out_resource->flag = std::move(flag);
+ std::string error;
+ auto flag_status = GetFlagStatus(out_resource->flag, options_.feature_flag_values, &error);
+ if (flag_status) {
+ out_resource->flag_status = flag_status.value();
+ } else {
+ diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << error);
+ return false;
+ }
+ } else if (options_.flag) {
+ out_resource->flag = options_.flag;
+ out_resource->flag_status = options_.flag_status;
}
// The value format accepted for this resource.
@@ -571,7 +581,7 @@
// Items have their type encoded in the type attribute.
if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
- resource_type = std::string(maybe_type.value());
+ resource_type = maybe_type.value();
} else {
diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
<< "<item> must have a 'type' attribute");
@@ -594,7 +604,7 @@
// Bags have their type encoded in the type attribute.
if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
- resource_type = std::string(maybe_type.value());
+ resource_type = maybe_type.value();
} else {
diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
<< "<bag> must have a 'type' attribute");
@@ -737,22 +747,6 @@
return false;
}
-std::optional<FeatureFlagAttribute> ResourceParser::GetFlag(xml::XmlPullParser* parser) {
- auto name = xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag");
- if (name) {
- FeatureFlagAttribute flag;
- if (name->starts_with('!')) {
- flag.negated = true;
- flag.name = name->substr(1);
- } else {
- flag.name = name.value();
- }
- return flag;
- } else {
- return {};
- }
-}
-
bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
ParsedResource* out_resource,
const uint32_t format) {
@@ -1659,7 +1653,7 @@
const std::string& element_namespace = parser->element_namespace();
const std::string& element_name = parser->element_name();
if (element_namespace.empty() && element_name == "item") {
- auto flag = GetFlag(parser);
+ auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"));
std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString);
if (!item) {
diag_->Error(android::DiagMessage(item_source) << "could not parse array item");
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index a789d3e..90690d5 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -57,6 +57,11 @@
std::optional<Visibility::Level> visibility;
FeatureFlagValues feature_flag_values;
+
+ // The flag that should be applied to all resources parsed
+ std::optional<FeatureFlagAttribute> flag;
+
+ FlagStatus flag_status = FlagStatus::NoFlag;
};
struct FlattenedXmlSubTree {
@@ -85,8 +90,6 @@
private:
DISALLOW_COPY_AND_ASSIGN(ResourceParser);
- std::optional<FeatureFlagAttribute> GetFlag(xml::XmlPullParser* parser);
-
std::optional<FlattenedXmlSubTree> CreateFlattenSubTree(xml::XmlPullParser* parser);
// Parses the XML subtree as a StyleString (flattened XML representation for strings with
diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto
index b0ed3da3..f4735a2 100644
--- a/tools/aapt2/ResourcesInternal.proto
+++ b/tools/aapt2/ResourcesInternal.proto
@@ -49,4 +49,9 @@
// Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file).
repeated Symbol exported_symbol = 5;
+
+ // The status of the flag the file is behind if any
+ uint32 flag_status = 6;
+ bool flag_negated = 7;
+ string flag_name = 8;
}
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 2a978a5..52372fa 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -67,6 +67,7 @@
std::string resource_dir;
std::string name;
std::string extension;
+ std::string flag_name;
// Original config str. We keep this because when we parse the config, we may add on
// version qualifiers. We want to preserve the original input so the output is easily
@@ -81,6 +82,22 @@
std::string* out_error,
const CompileOptions& options) {
std::vector<std::string> parts = util::Split(path, dir_sep);
+
+ std::string flag_name;
+ // Check for a flag
+ for (auto iter = parts.begin(); iter != parts.end();) {
+ if (iter->starts_with("flag(") && iter->ends_with(")")) {
+ if (!flag_name.empty()) {
+ if (out_error) *out_error = "resource path cannot contain more than one flag directory";
+ return {};
+ }
+ flag_name = iter->substr(5, iter->size() - 6);
+ iter = parts.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+
if (parts.size() < 2) {
if (out_error) *out_error = "bad resource path";
return {};
@@ -131,6 +148,7 @@
std::string(dir_str),
std::string(name),
std::string(extension),
+ std::move(flag_name),
std::string(config_str),
config};
}
@@ -142,6 +160,9 @@
name << "-" << data.config_str;
}
name << "_" << data.name;
+ if (!data.flag_name.empty()) {
+ name << ".(" << data.flag_name << ")";
+ }
if (!data.extension.empty()) {
name << "." << data.extension;
}
@@ -163,7 +184,6 @@
<< "failed to open file: " << fin->GetError());
return false;
}
-
// Parse the values file from XML.
xml::XmlPullParser xml_parser(fin.get());
@@ -176,6 +196,18 @@
// If visibility was forced, we need to use it when creating a new resource and also error if
// we try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
parser_options.visibility = options.visibility;
+ parser_options.flag = ParseFlag(path_data.flag_name);
+
+ if (parser_options.flag) {
+ std::string error;
+ auto flag_status = GetFlagStatus(parser_options.flag, options.feature_flag_values, &error);
+ if (flag_status) {
+ parser_options.flag_status = std::move(flag_status.value());
+ } else {
+ context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << error);
+ return false;
+ }
+ }
ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config,
parser_options);
@@ -402,6 +434,18 @@
xmlres->file.config = path_data.config;
xmlres->file.source = path_data.source;
xmlres->file.type = ResourceFile::Type::kProtoXml;
+ xmlres->file.flag = ParseFlag(path_data.flag_name);
+
+ if (xmlres->file.flag) {
+ std::string error;
+ auto flag_status = GetFlagStatus(xmlres->file.flag, options.feature_flag_values, &error);
+ if (flag_status) {
+ xmlres->file.flag_status = flag_status.value();
+ } else {
+ context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << error);
+ return false;
+ }
+ }
// Collect IDs that are defined here.
XmlIdCollector collector;
@@ -491,6 +535,27 @@
res_file.source = path_data.source;
res_file.type = ResourceFile::Type::kPng;
+ if (!path_data.flag_name.empty()) {
+ FeatureFlagAttribute flag;
+ auto name = path_data.flag_name;
+ if (name.starts_with('!')) {
+ flag.negated = true;
+ flag.name = name.substr(1);
+ } else {
+ flag.name = name;
+ }
+ res_file.flag = flag;
+
+ std::string error;
+ auto flag_status = GetFlagStatus(flag, options.feature_flag_values, &error);
+ if (flag_status) {
+ res_file.flag_status = flag_status.value();
+ } else {
+ context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << error);
+ return false;
+ }
+ }
+
{
auto data = file->OpenAsData();
if (!data) {
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 2177c34..08f8f0d 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -34,6 +34,20 @@
namespace aapt {
+std::optional<FeatureFlagAttribute> ParseFlag(std::optional<std::string_view> flag_text) {
+ if (!flag_text || flag_text->empty()) {
+ return {};
+ }
+ FeatureFlagAttribute flag;
+ if (flag_text->starts_with('!')) {
+ flag.negated = true;
+ flag.name = flag_text->substr(1);
+ } else {
+ flag.name = flag_text.value();
+ }
+ return flag;
+}
+
std::optional<FlagStatus> GetFlagStatus(const std::optional<FeatureFlagAttribute>& flag,
const FeatureFlagValues& feature_flag_values,
std::string* out_err) {
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index f8e44b7..d32e532 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -49,6 +49,8 @@
using FeatureFlagValues = std::map<std::string, FeatureFlagProperties, std::less<>>;
+std::optional<FeatureFlagAttribute> ParseFlag(std::optional<std::string_view> flag_text);
+
std::optional<FlagStatus> GetFlagStatus(const std::optional<FeatureFlagAttribute>& flag,
const FeatureFlagValues& feature_flag_values,
std::string* out_err);
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index c6bd6dd..8583cad 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -643,6 +643,12 @@
out_file->source.path = pb_file.source_path();
out_file->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
+ out_file->flag_status = (FlagStatus)pb_file.flag_status();
+ if (!pb_file.flag_name().empty()) {
+ out_file->flag =
+ FeatureFlagAttribute{.name = pb_file.flag_name(), .negated = pb_file.flag_negated()};
+ }
+
std::string config_error;
if (!DeserializeConfigFromPb(pb_file.config(), &out_file->config, &config_error)) {
std::ostringstream error;
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 9c28780..d83fe91 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -755,6 +755,11 @@
out_file->set_source_path(file.source.path);
out_file->set_type(SerializeFileReferenceTypeToPb(file.type));
SerializeConfig(file.config, out_file->mutable_config());
+ out_file->set_flag_status((uint32_t)file.flag_status);
+ if (file.flag) {
+ out_file->set_flag_negated(file.flag->negated);
+ out_file->set_flag_name(file.flag->name);
+ }
for (const SourcedResourceName& exported : file.exported_symbols) {
pb::internal::CompiledFile_Symbol* pb_symbol = out_file->add_exported_symbol();
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
index c456e5c..7160b35 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
@@ -31,13 +31,29 @@
"res/values/ints.xml",
"res/values/strings.xml",
"res/layout/layout1.xml",
+ "res/layout/layout3.xml",
+ "res/flag(test.package.falseFlag)/values/bools.xml",
+ "res/flag(test.package.falseFlag)/layout/layout2.xml",
+ "res/flag(test.package.falseFlag)/drawable/removedpng.png",
+ "res/flag(test.package.trueFlag)/layout/layout3.xml",
+ "res/values/flag(test.package.trueFlag)/bools.xml",
+ "res/values/flag(!test.package.trueFlag)/bools.xml",
+ "res/values/flag(!test.package.falseFlag)/bools.xml",
],
out: [
+ "drawable_removedpng.(test.package.falseFlag).png.flat",
"values_bools.arsc.flat",
+ "values_bools.(test.package.falseFlag).arsc.flat",
+ "values_bools.(test.package.trueFlag).arsc.flat",
+ "values_bools.(!test.package.falseFlag).arsc.flat",
+ "values_bools.(!test.package.trueFlag).arsc.flat",
"values_bools2.arsc.flat",
"values_ints.arsc.flat",
"values_strings.arsc.flat",
"layout_layout1.xml.flat",
+ "layout_layout2.(test.package.falseFlag).xml.flat",
+ "layout_layout3.xml.flat",
+ "layout_layout3.(test.package.trueFlag).xml.flat",
],
cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
"--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/drawable/removedpng.png" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/drawable/removedpng.png"
new file mode 100644
index 0000000..8a9e698
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/drawable/removedpng.png"
Binary files differ
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/layout/layout2.xml" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/layout/layout2.xml"
new file mode 100644
index 0000000..dec5de7
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/layout/layout2.xml"
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+</LinearLayout>
\ No newline at end of file
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/values/bools.xml" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/values/bools.xml"
new file mode 100644
index 0000000..c46c4d4
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/values/bools.xml"
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <bool name="bool7">false</bool>
+</resources>
\ No newline at end of file
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.trueFlag\051/layout/layout3.xml" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.trueFlag\051/layout/layout3.xml"
new file mode 100644
index 0000000..5aeee0e
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.trueFlag\051/layout/layout3.xml"
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ <TextView android:id="@+id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="foobar" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml
new file mode 100644
index 0000000..dec5de7
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
index 7837e17..35975ed 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
@@ -15,4 +15,9 @@
<bool name="bool6">true</bool>
<bool name="bool6" android:featureFlag="!test.package.trueFlag">false</bool>
+
+ <bool name="bool7">true</bool>
+ <bool name="bool8">false</bool>
+ <bool name="bool9">true</bool>
+ <bool name="bool10">false</bool>
</resources>
\ No newline at end of file
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050\041test.package.falseFlag\051/bools.xml" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050\041test.package.falseFlag\051/bools.xml"
new file mode 100644
index 0000000..a63749c
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050\041test.package.falseFlag\051/bools.xml"
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <bool name="bool10">true</bool>
+</resources>
\ No newline at end of file
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050\041test.package.trueFlag\051/bools.xml" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050\041test.package.trueFlag\051/bools.xml"
new file mode 100644
index 0000000..bb5526e
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050\041test.package.trueFlag\051/bools.xml"
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <bool name="bool9">false</bool>
+</resources>
\ No newline at end of file
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050test.package.trueFlag\051/bools.xml" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050test.package.trueFlag\051/bools.xml"
new file mode 100644
index 0000000..eba780e
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050test.package.trueFlag\051/bools.xml"
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <bool name="bool8">true</bool>
+</resources>
\ No newline at end of file
diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
index 67d0c41..6293008 100644
--- a/tools/aapt2/link/FlaggedResources_test.cpp
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -76,6 +76,10 @@
std::string output;
DumpResourceTableToString(loaded_apk.get(), &output);
+ ASSERT_EQ(output.find("bool4"), std::string::npos);
+ ASSERT_EQ(output.find("str1"), std::string::npos);
+ ASSERT_EQ(output.find("layout2"), std::string::npos);
+ ASSERT_EQ(output.find("removedpng"), std::string::npos);
}
TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTableChunks) {
@@ -87,6 +91,8 @@
ASSERT_EQ(output.find("bool4"), std::string::npos);
ASSERT_EQ(output.find("str1"), std::string::npos);
+ ASSERT_EQ(output.find("layout2"), std::string::npos);
+ ASSERT_EQ(output.find("removedpng"), std::string::npos);
}
TEST_F(FlaggedResourcesTest, DisabledResourcesInRJava) {
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index d216979..1bef5f8 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -377,6 +377,8 @@
file_ref->SetSource(file_desc.source);
file_ref->type = file_desc.type;
file_ref->file = file;
+ file_ref->SetFlagStatus(file_desc.flag_status);
+ file_ref->SetFlag(file_desc.flag);
ResourceTablePackage* pkg = table.FindOrCreatePackage(file_desc.name.package);
pkg->FindOrCreateType(file_desc.name.type)