diff --git a/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml b/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml
new file mode 100644
index 0000000000000..dad2cdf8ede15
--- /dev/null
+++ b/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 885e5e2d4441a..50dcaf333ea1f 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -111,5 +111,15 @@
android:visibility="gone"
android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off" />
+
+
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0f9462866cd49..71e325d7272a1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2798,6 +2798,8 @@
Mic is off
Camera and mic are off
+
+ Assistant is listening
{count, plural,
=1 {# notification}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 48f90bc608b7a..caa0e96cf5fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -28,6 +28,7 @@ import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.UdfpsOverlay
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.dagger.qualifiers.PerUser
+import com.android.systemui.dreams.AssistantAttentionMonitor
import com.android.systemui.dreams.DreamMonitor
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.keyboard.KeyboardUI
@@ -309,4 +310,10 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(DreamMonitor::class)
abstract fun bindDreamMonitor(sysui: DreamMonitor): CoreStartable
+
+ /**Inject into AssistantAttentionMonitor */
+ @Binds
+ @IntoMap
+ @ClassKey(AssistantAttentionMonitor::class)
+ abstract fun bindAssistantAttentionMonitor(sysui: AssistantAttentionMonitor): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/AssistantAttentionMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/AssistantAttentionMonitor.java
new file mode 100644
index 0000000000000..49d7f78107402
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/AssistantAttentionMonitor.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import android.util.Log;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.callbacks.AssistantAttentionCallback;
+import com.android.systemui.dreams.conditions.AssistantAttentionCondition;
+import com.android.systemui.shared.condition.Monitor;
+
+import javax.inject.Inject;
+
+/**
+ * A {@link CoreStartable} to retain a monitor for tracking assistant attention.
+ */
+public class AssistantAttentionMonitor implements CoreStartable {
+ private static final String TAG = "AssistAttentionMonitor";
+
+ // We retain a reference to the monitor so it is not garbage-collected.
+ private final Monitor mConditionMonitor;
+ private final AssistantAttentionCondition mAssistantAttentionCondition;
+ private final AssistantAttentionCallback mCallback;
+
+ @Inject
+ public AssistantAttentionMonitor(
+ Monitor monitor,
+ AssistantAttentionCondition assistantAttentionCondition,
+ AssistantAttentionCallback callback) {
+ mConditionMonitor = monitor;
+ mAssistantAttentionCondition = assistantAttentionCondition;
+ mCallback = callback;
+
+ }
+ @Override
+ public void start() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "started");
+ }
+
+ mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
+ .addCondition(mAssistantAttentionCondition)
+ .build());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 2c7ecb1182f27..b7f6a70ecad85 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -56,6 +56,8 @@ public class DreamOverlayStateController implements
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
public static final int STATE_DREAM_EXIT_ANIMATIONS_RUNNING = 1 << 3;
+ public static final int STATE_HAS_ASSISTANT_ATTENTION = 1 << 4;
+ public static final int STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE = 1 << 5;
private static final int OP_CLEAR_STATE = 1;
private static final int OP_SET_STATE = 2;
@@ -251,6 +253,22 @@ public class DreamOverlayStateController implements
return containsState(STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
}
+ /**
+ * Returns whether assistant currently has the user's attention.
+ * @return {@code true} if assistant has the user's attention, {@code false} otherwise.
+ */
+ public boolean hasAssistantAttention() {
+ return containsState(STATE_HAS_ASSISTANT_ATTENTION);
+ }
+
+ /**
+ * Returns whether the dream overlay status bar is currently visible.
+ * @return {@code true} if the status bar is visible, {@code false} otherwise.
+ */
+ public boolean isDreamOverlayStatusBarVisible() {
+ return containsState(STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
+ }
+
private boolean containsState(int state) {
return (mState & state) != 0;
}
@@ -309,6 +327,23 @@ public class DreamOverlayStateController implements
STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
}
+ /**
+ * Sets whether assistant currently has the user's attention.
+ * @param hasAttention {@code true} if has the user's attention, {@code false} otherwise.
+ */
+ public void setHasAssistantAttention(boolean hasAttention) {
+ modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION);
+ }
+
+ /**
+ * Sets whether the dream overlay status bar is visible.
+ * @param visible {@code true} if the status bar is visible, {@code false} otherwise.
+ */
+ public void setDreamOverlayStatusBarVisible(boolean visible) {
+ modifyState(
+ visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
+ }
+
/**
* Returns the available complication types.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 96bce4cd3cd9f..7c1bfeda30b28 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -52,7 +52,8 @@ public class DreamOverlayStatusBarView extends ConstraintLayout {
STATUS_ICON_CAMERA_DISABLED,
STATUS_ICON_MIC_DISABLED,
STATUS_ICON_MIC_CAMERA_DISABLED,
- STATUS_ICON_PRIORITY_MODE_ON
+ STATUS_ICON_PRIORITY_MODE_ON,
+ STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
})
public @interface StatusIconType {}
public static final int STATUS_ICON_NOTIFICATIONS = 0;
@@ -62,6 +63,7 @@ public class DreamOverlayStatusBarView extends ConstraintLayout {
public static final int STATUS_ICON_MIC_DISABLED = 4;
public static final int STATUS_ICON_MIC_CAMERA_DISABLED = 5;
public static final int STATUS_ICON_PRIORITY_MODE_ON = 6;
+ public static final int STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE = 7;
private final Map mStatusIcons = new HashMap<>();
private Context mContext;
@@ -132,6 +134,8 @@ public class DreamOverlayStatusBarView extends ConstraintLayout {
fetchStatusIconForResId(R.id.dream_overlay_notification_indicator));
mStatusIcons.put(STATUS_ICON_PRIORITY_MODE_ON,
addDoubleShadow(fetchStatusIconForResId(R.id.dream_overlay_priority_mode)));
+ mStatusIcons.put(STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
+ fetchStatusIconForResId(R.id.dream_overlay_assistant_attention_indicator));
mSystemStatusViewGroup = findViewById(R.id.dream_overlay_system_status);
mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 90c440c403ec4..2221a04d3aa42 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -113,6 +113,7 @@ public class DreamOverlayStatusBarViewController extends ViewController provider.removeCallback(mNotificationCountCallback));
mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback);
mView.removeAllExtraStatusBarItemViews();
+ mDreamOverlayStateController.setDreamOverlayStatusBarVisible(false);
mDreamOverlayStateController.removeCallback(mDreamOverlayStateCallback);
mTouchInsetSession.clear();
@@ -270,12 +272,20 @@ public class DreamOverlayStatusBarViewController extends ViewController callbackCapture =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
+ callbackCapture.getValue().onStateChanged();
+
+ verify(mView).showIcon(
+ DreamOverlayStatusBarView.STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE, true, null);
+ }
+
@Test
public void testStatusBarHiddenWhenSystemStatusBarShown() {
mController.onViewAttached();
@@ -572,6 +587,13 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
}
+ @Test
+ public void testDreamOverlayStatusBarVisibleSetToFalseOnDetach() {
+ mController.onViewAttached();
+ mController.onViewDetached();
+ verify(mDreamOverlayStateController).setDreamOverlayStatusBarVisible(false);
+ }
+
private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
final ArgumentCaptor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
new file mode 100644
index 0000000000000..ef1061faeed9d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.conditions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.app.AssistUtils;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Condition;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class AssistantAttentionConditionTest extends SysuiTestCase {
+ @Mock
+ Condition.Callback mCallback;
+ @Mock
+ AssistUtils mAssistUtils;
+ @Mock
+ DreamOverlayStateController mDreamOverlayStateController;
+
+ private AssistantAttentionCondition mAssistantAttentionCondition;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mAssistantAttentionCondition =
+ new AssistantAttentionCondition(mDreamOverlayStateController, mAssistUtils);
+ // Adding a callback also starts the condition.
+ mAssistantAttentionCondition.addCallback(mCallback);
+ }
+
+ @Test
+ public void testEnableVisualQueryDetection() {
+ final ArgumentCaptor argumentCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(argumentCaptor.capture());
+
+ when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
+ argumentCaptor.getValue().onStateChanged();
+
+ verify(mAssistUtils).enableVisualQueryDetection(any());
+ }
+
+ @Test
+ public void testDisableVisualQueryDetection() {
+ final ArgumentCaptor argumentCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(argumentCaptor.capture());
+
+ when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
+ argumentCaptor.getValue().onStateChanged();
+ when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(false);
+ argumentCaptor.getValue().onStateChanged();
+
+ verify(mAssistUtils).disableVisualQueryDetection();
+ }
+
+ @Test
+ public void testAttentionChangedTriggersCondition() throws RemoteException {
+ final ArgumentCaptor callbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(callbackCaptor.capture());
+
+ when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
+ callbackCaptor.getValue().onStateChanged();
+
+ final ArgumentCaptor listenerCaptor =
+ ArgumentCaptor.forClass(IVisualQueryDetectionAttentionListener.class);
+ verify(mAssistUtils).enableVisualQueryDetection(listenerCaptor.capture());
+
+ listenerCaptor.getValue().onAttentionGained();
+ assertThat(mAssistantAttentionCondition.isConditionMet()).isTrue();
+
+ listenerCaptor.getValue().onAttentionLost();
+ assertThat(mAssistantAttentionCondition.isConditionMet()).isFalse();
+
+ verify(mCallback, times(2)).onConditionChanged(eq(mAssistantAttentionCondition));
+ }
+}