Track dream focus in order to know when dreams are occluded.
One of the changes needed to return to a dream on power button press if
the dream is occluded by something like the notification shade or the
bouncer.
Bug: 331798001
Test: atest DreamServiceTests
Flag: ACONFIG android.service.dreams.dream_tracks_focus STAGING
Change-Id: Iebb5470b7bdda869d46cdb7ffb6bedb580810ae5
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 9a02b74b..5da0cb4 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -79,6 +79,11 @@
mService.endDream(this);
}
+ @Override
+ public void comeToFront() {
+ mService.comeToFront(this);
+ }
+
private void onExitRequested() {
try {
mDreamOverlayCallback.onExitRequested();
@@ -130,6 +135,16 @@
});
}
+ private void comeToFront(OverlayClient client) {
+ mExecutor.execute(() -> {
+ if (mCurrentClient != client) {
+ return;
+ }
+
+ onComeToFront();
+ });
+ }
+
private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
@Override
public void getClient(IDreamOverlayClientCallback callback) {
@@ -190,6 +205,13 @@
public void onWakeUp() {}
/**
+ * This method is overridden by implementations to handle when the dream is coming to the front
+ * (after having lost focus to something on top of it).
+ * @hide
+ */
+ public void onComeToFront() {}
+
+ /**
* This method is overridden by implementations to handle when the dream has ended. There may
* be earlier signals leading up to this step, such as @{@link #onWakeUp(Runnable)}.
*
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 353828c..5d8b608 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -18,6 +18,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.service.dreams.Flags.dreamHandlesConfirmKeys;
+import static android.service.dreams.Flags.dreamTracksFocus;
import android.annotation.FlaggedApi;
import android.annotation.IdRes;
@@ -455,6 +456,15 @@
/** {@inheritDoc} */
@Override
public void onWindowFocusChanged(boolean hasFocus) {
+ if (!dreamTracksFocus()) {
+ return;
+ }
+
+ try {
+ mDreamManager.onDreamFocusChanged(hasFocus);
+ } catch (RemoteException ex) {
+ // system server died
+ }
}
/** {@inheritDoc} */
@@ -1149,6 +1159,19 @@
wakeUp(false);
}
+ /**
+ * Tells the dream to come to the front (which in turn tells the overlay to come to the front).
+ */
+ private void comeToFront() {
+ mOverlayConnection.addConsumer(overlay -> {
+ try {
+ overlay.comeToFront();
+ } catch (RemoteException e) {
+ Log.e(mTag, "could not tell overlay to come to front:" + e);
+ }
+ });
+ }
+
private void wakeUp(boolean fromSystem) {
if (mDebug) {
Slog.v(mTag, "wakeUp(): fromSystem=" + fromSystem + ", mWaking=" + mWaking
@@ -1596,6 +1619,15 @@
public void wakeUp() {
mHandler.post(() -> DreamService.this.wakeUp(true /*fromSystem*/));
}
+
+ @Override
+ public void comeToFront() {
+ if (!dreamTracksFocus()) {
+ return;
+ }
+
+ mHandler.post(DreamService.this::comeToFront);
+ }
}
/** @hide */
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index e45384f..85f0368 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -48,4 +48,5 @@
void setSystemDreamComponent(in ComponentName componentName);
void registerDreamOverlayService(in ComponentName componentName);
void startDreamActivity(in Intent intent);
+ void onDreamFocusChanged(in boolean hasFocus);
}
diff --git a/core/java/android/service/dreams/IDreamOverlayClient.aidl b/core/java/android/service/dreams/IDreamOverlayClient.aidl
index 78b7280..5054d4d 100644
--- a/core/java/android/service/dreams/IDreamOverlayClient.aidl
+++ b/core/java/android/service/dreams/IDreamOverlayClient.aidl
@@ -42,4 +42,7 @@
/** Called when the dream has ended. */
void endDream();
+
+ /** Called when the dream is coming to the front. */
+ void comeToFront();
}
diff --git a/core/java/android/service/dreams/IDreamService.aidl b/core/java/android/service/dreams/IDreamService.aidl
index 8b5d875..2e2651b 100644
--- a/core/java/android/service/dreams/IDreamService.aidl
+++ b/core/java/android/service/dreams/IDreamService.aidl
@@ -25,4 +25,5 @@
void attach(IBinder windowToken, boolean canDoze, boolean isPreviewMode, IRemoteCallback started);
void detach();
void wakeUp();
+ void comeToFront();
}
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index 88f1090..0a458bc 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -19,3 +19,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "dream_tracks_focus"
+ namespace: "communal"
+ description: "This flag enables the ability for dreams to track whether or not they have focus"
+ bug: "331798001"
+}
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 0e8a5fb..a818eab 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -249,6 +249,16 @@
mCurrentDream.mAppTask = appTask;
}
+ void setDreamHasFocus(boolean hasFocus) {
+ if (mCurrentDream != null) {
+ mCurrentDream.mDreamHasFocus = hasFocus;
+ }
+ }
+
+ boolean dreamHasFocus() {
+ return mCurrentDream != null && mCurrentDream.mDreamHasFocus;
+ }
+
/**
* Sends a user activity signal to PowerManager to stop the screen from turning off immediately
* if there hasn't been any user interaction in a while.
@@ -271,6 +281,21 @@
stopDreamInstance(immediate, reason, mCurrentDream);
}
+ public boolean bringDreamToFront() {
+ if (mCurrentDream == null || mCurrentDream.mService == null) {
+ return false;
+ }
+
+ try {
+ mCurrentDream.mService.comeToFront();
+ return true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error asking dream to come to the front", e);
+ }
+
+ return false;
+ }
+
/**
* Stops the given dream instance.
*
@@ -426,6 +451,7 @@
private String mStopReason;
private long mDreamStartTime;
public boolean mWakingGently;
+ public boolean mDreamHasFocus;
private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded;
private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 42c9e08..fc63494 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.service.dreams.Flags.dreamTracksFocus;
import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;
@@ -406,8 +407,10 @@
/** Whether dreaming can start given user settings and the current dock/charge state. */
private boolean canStartDreamingInternal(boolean isScreenOn) {
synchronized (mLock) {
- // Can't start dreaming if we are already dreaming.
- if (isScreenOn && isDreamingInternal()) {
+ // Can't start dreaming if we are already dreaming and the dream has focus. If we are
+ // dreaming but the dream does not have focus, then the dream can be brought to the
+ // front so it does have focus.
+ if (isScreenOn && isDreamingInternal() && dreamHasFocus()) {
return false;
}
@@ -442,11 +445,20 @@
}
}
+ private boolean dreamHasFocus() {
+ // Dreams always had focus before they were able to track it.
+ return !dreamTracksFocus() || mController.dreamHasFocus();
+ }
+
protected void requestStartDreamFromShell() {
requestDreamInternal();
}
private void requestDreamInternal() {
+ if (isDreamingInternal() && !dreamHasFocus() && mController.bringDreamToFront()) {
+ return;
+ }
+
// Ask the power manager to nap. It will eventually call back into
// startDream() if/when it is appropriate to start dreaming.
// Because napping could cause the screen to turn off immediately if the dream
@@ -1128,6 +1140,16 @@
});
}
+ @Override
+ public void onDreamFocusChanged(boolean hasFocus) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mController.setDreamHasFocus(hasFocus);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
boolean canLaunchDreamActivity(String dreamPackageName, String packageName,
int callingUid) {
if (dreamPackageName == null || packageName == null) {
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java
index db70434..88ab871 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java
@@ -19,6 +19,8 @@
import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER;
import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -270,6 +272,31 @@
eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS));
}
+ @Test
+ public void setDreamHasFocus_true_dreamHasFocus() {
+ mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+ 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+
+ mDreamController.setDreamHasFocus(true);
+ assertTrue(mDreamController.dreamHasFocus());
+ }
+
+ @Test
+ public void setDreamHasFocus_false_dreamDoesNotHaveFocus() {
+ mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+ 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+
+ mDreamController.setDreamHasFocus(false);
+ assertFalse(mDreamController.dreamHasFocus());
+ }
+
+ @Test
+ public void setDreamHasFocus_notDreaming_dreamDoesNotHaveFocus() {
+ mDreamController.setDreamHasFocus(true);
+ // Dream still doesn't have focus because it was never started.
+ assertFalse(mDreamController.dreamHasFocus());
+ }
+
private ServiceConnection captureServiceConnection() {
verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(),
any());