Synchronize bubble activity rendering status and its visibility change.
- Add an API ITaskStackListener.onSingleTaskDisplayDrawn() to notifity contents
are drawn for the first time on a display which can only contain one task.
- BubbleController updates contents visibility (actually alpha value) of
the Surface in a ActivityView.
Bug: 130442248
Test: atest WmTests:TaskStackChangedListenerTest
Change-Id: Ie5aed373996419b059935889b564ca91c2e3cf23
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 4771f9f..3bf659b 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -120,6 +120,7 @@
mActivityTaskManager = ActivityTaskManager.getService();
mSurfaceView = new SurfaceView(context);
+ mSurfaceView.setAlpha(0f);
mSurfaceCallback = new SurfaceCallback();
mSurfaceView.getHolder().addCallback(mSurfaceCallback);
addView(mSurfaceView);
@@ -347,6 +348,16 @@
}
@Override
+ public void setAlpha(float alpha) {
+ mSurfaceView.setAlpha(alpha);
+ }
+
+ @Override
+ public float getAlpha() {
+ return mSurfaceView.getAlpha();
+ }
+
+ @Override
public boolean gatherTransparentRegion(Region region) {
// The tap exclude region may be affected by any view on top of it, so we detect the
// possible change by monitoring this function.
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 1fdc8ca5..3f6880f 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -169,4 +169,12 @@
* @param taskInfo info about the task which received the back press
*/
void onBackPressedOnTaskRoot(in ActivityManager.RunningTaskInfo taskInfo);
+
+ /*
+ * Called when contents are drawn for the first time on a display which can only contain one
+ * task.
+ *
+ * @param displayId the id of the display on which contents are drawn.
+ */
+ void onSingleTaskDisplayDrawn(int displayId);
}
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 00f3ad5..36daf32 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -173,4 +173,8 @@
public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo)
throws RemoteException {
}
+
+ @Override
+ public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
+ }
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 254d04e..add7376b 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -27,6 +27,7 @@
import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.HardwareRenderer;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
@@ -201,6 +202,29 @@
private SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction();
+ /**
+ * A callback which reflects an alpha value of this view onto the underlying surfaces.
+ *
+ * <p class="note"><strong>Note:</strong> This doesn't have to be defined as a member variable,
+ * but can be defined as an inline lambda when calling ViewRootImpl#registerRtFrameCallback().
+ * However when we do so, the callback is triggered only for a few times and stops working for
+ * some reason. It's suspected that there is a problem around garbage collection, and until
+ * the cause is fixed, we will keep this callback in a member variable.</p>
+ */
+ private HardwareRenderer.FrameDrawingCallback mSetSurfaceAlphaCallback = frame -> {
+ final ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
+ // In this case, the alpha value is reflected on the screen in #updateSurface() later.
+ return;
+ }
+
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setAlpha(mSurfaceControl, getAlpha());
+ t.deferTransactionUntilSurface(mSurfaceControl, viewRoot.mSurface, frame);
+ t.setEarlyWakeup();
+ t.apply();
+ };
+
public SurfaceView(Context context) {
this(context, null);
}
@@ -288,6 +312,17 @@
updateSurface();
}
+ @Override
+ public void setAlpha(float alpha) {
+ super.setAlpha(alpha);
+ final ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot == null) {
+ return;
+ }
+ viewRoot.registerRtFrameCallback(mSetSurfaceAlphaCallback);
+ invalidate();
+ }
+
private void performDrawFinished() {
if (mPendingReportDraws > 0) {
mDrawFinished = true;
@@ -647,6 +682,13 @@
}
updateBackgroundVisibilityInTransaction(viewRoot.getSurfaceControl());
+ // Alpha value change is handled in setAlpha() directly using a local
+ // transaction. However it can happen that setAlpha() is called while
+ // local transactions cannot be applied, so the value is stored in a View
+ // but not yet reflected on the Surface.
+ mSurfaceControl.setAlpha(getAlpha());
+ mBackgroundControl.setAlpha(getAlpha());
+
// While creating the surface, we will set it's initial
// geometry. Outside of that though, we should generally
// leave it to the RenderThread.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index a25f2ee..1f89de8 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -261,6 +261,13 @@
int TRANSIT_TASK_CHANGE_WINDOWING_MODE = 27;
/**
+ * A display which can only contain one task is being shown because the first activity is
+ * started or it's being turned on.
+ * @hide
+ */
+ int TRANSIT_SHOW_SINGLE_TASK_DISPLAY = 28;
+
+ /**
* @hide
*/
@IntDef(prefix = { "TRANSIT_" }, value = {
@@ -287,7 +294,8 @@
TRANSIT_TRANSLUCENT_ACTIVITY_OPEN,
TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE,
TRANSIT_CRASHING_ACTIVITY_CLOSE,
- TRANSIT_TASK_CHANGE_WINDOWING_MODE
+ TRANSIT_TASK_CHANGE_WINDOWING_MODE,
+ TRANSIT_SHOW_SINGLE_TASK_DISPLAY
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionType {}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index 21b3a00..bd2b19c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -64,6 +64,14 @@
onActivityLaunchOnSecondaryDisplayRerouted();
}
+ /**
+ * Called when contents are drawn for the first time on a display which can only contain one
+ * task.
+ *
+ * @param displayId the id of the display on which contents are drawn.
+ */
+ public void onSingleTaskDisplayDrawn(int displayId) { }
+
public void onTaskProfileLocked(int taskId, int userId) { }
public void onTaskCreated(int taskId, ComponentName componentName) { }
public void onTaskRemoved(int taskId) { }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index 06ae399..c89f2ab 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -196,11 +196,18 @@
}
@Override
- public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
+ public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken)
+ throws RemoteException {
mHandler.obtainMessage(H.ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId, 0 /* unused */,
activityToken).sendToTarget();
}
+ @Override
+ public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
+ mHandler.obtainMessage(H.ON_SINGLE_TASK_DISPLAY_DRAWN, displayId,
+ 0 /* unused */).sendToTarget();
+ }
+
private final class H extends Handler {
private static final int ON_TASK_STACK_CHANGED = 1;
private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
@@ -220,6 +227,7 @@
private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED = 16;
private static final int ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED = 17;
private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 18;
+ private static final int ON_SINGLE_TASK_DISPLAY_DRAWN = 19;
public H(Looper looper) {
@@ -356,6 +364,12 @@
}
break;
}
+ case ON_SINGLE_TASK_DISPLAY_DRAWN: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onSingleTaskDisplayDrawn(msg.arg1);
+ }
+ break;
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index f60e95e..5c6c397 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -16,6 +16,8 @@
package com.android.systemui.bubbles;
+import static android.view.Display.INVALID_DISPLAY;
+
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import android.content.Context;
@@ -129,6 +131,20 @@
mInflated = true;
}
+ /**
+ * Set visibility of bubble in the expanded state.
+ *
+ * @param visibility {@code true} if the expanded bubble should be visible on the screen.
+ *
+ * Note that this contents visibility doesn't affect visibility at {@link android.view.View},
+ * and setting {@code false} actually means rendering the expanded view in transparent.
+ */
+ void setContentVisibility(boolean visibility) {
+ if (expandedView != null) {
+ expandedView.setContentVisibility(visibility);
+ }
+ }
+
void setDismissed() {
entry.setBubbleDismissed(true);
// TODO: move this somewhere where it can be guaranteed not to run until safe from flicker
@@ -168,6 +184,13 @@
}
/**
+ * @return the display id of the virtual display on which bubble contents is drawn.
+ */
+ int getDisplayId() {
+ return expandedView != null ? expandedView.getVirtualDisplayId() : INVALID_DISPLAY;
+ }
+
+ /**
* Should be invoked whenever a Bubble is accessed (selected while expanded).
*/
void markAsAccessedAt(long lastAccessedMillis) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index dcc0419..c2f4cff 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -603,17 +603,23 @@
* status bar, otherwise returns {@link Display#INVALID_DISPLAY}.
*/
public int getExpandedDisplayId(Context context) {
+ final Bubble bubble = getExpandedBubble(context);
+ return bubble != null ? bubble.getDisplayId() : INVALID_DISPLAY;
+ }
+
+ @Nullable
+ private Bubble getExpandedBubble(Context context) {
if (mStackView == null) {
- return INVALID_DISPLAY;
+ return null;
}
- boolean defaultDisplay = context.getDisplay() != null
+ final boolean defaultDisplay = context.getDisplay() != null
&& context.getDisplay().getDisplayId() == DEFAULT_DISPLAY;
- Bubble b = mStackView.getExpandedBubble();
- if (defaultDisplay && b != null && isStackExpanded()
+ final Bubble expandedBubble = mStackView.getExpandedBubble();
+ if (defaultDisplay && expandedBubble != null && isStackExpanded()
&& !mStatusBarWindowController.getPanelExpanded()) {
- return b.expandedView.getVirtualDisplayId();
+ return expandedBubble;
}
- return INVALID_DISPLAY;
+ return null;
}
@VisibleForTesting
@@ -730,6 +736,14 @@
mBubbleData.setExpanded(false);
}
}
+
+ @Override
+ public void onSingleTaskDisplayDrawn(int displayId) {
+ final Bubble expandedBubble = getExpandedBubble(mContext);
+ if (expandedBubble != null && expandedBubble.getDisplayId() == displayId) {
+ expandedBubble.setContentVisibility(true);
+ }
+ }
}
private static boolean shouldAutoBubbleMessages(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index e7948b5..a4a2d9e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -185,6 +185,8 @@
mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
true /* singleTaskInstance */);
+
+ setContentVisibility(false);
addView(mActivityView);
// Expanded stack layout, top to bottom:
@@ -239,6 +241,22 @@
}
/**
+ * Set visibility of contents in the expanded state.
+ *
+ * @param visibility {@code true} if the contents should be visible on the screen.
+ *
+ * Note that this contents visibility doesn't affect visibility at {@link android.view.View},
+ * and setting {@code false} actually means rendering the contents in transparent.
+ */
+ void setContentVisibility(boolean visibility) {
+ final float alpha = visibility ? 1f : 0f;
+ mPointerView.setAlpha(alpha);
+ if (mActivityView != null) {
+ mActivityView.setAlpha(alpha);
+ }
+ }
+
+ /**
* Called by {@link BubbleStackView} when the insets for the expanded state should be updated.
* This should be done post-move and post-animation.
*/
@@ -310,6 +328,7 @@
parent.removeView(mNotifRow);
}
addView(mNotifRow, 1 /* index */);
+ mPointerView.setAlpha(1f);
}
}
@@ -336,12 +355,12 @@
removeView(mNotifRow);
mNotifRow = null;
}
+ setContentVisibility(false);
mActivityView.setVisibility(VISIBLE);
} else {
// Hide activity view if we had it previously
mActivityView.setVisibility(GONE);
mNotifRow = mEntry.getRow();
-
}
updateView();
}
@@ -440,6 +459,7 @@
mActivityView.onLocationChanged();
} else if (mNotifRow != null) {
applyRowState(mNotifRow);
+ mPointerView.setAlpha(1f);
}
updateHeight();
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index bec90d2..14d8f37 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -740,12 +740,16 @@
}
final Bubble previouslySelected = mExpandedBubble;
mExpandedBubble = bubbleToSelect;
+
if (mIsExpanded) {
// Make the container of the expanded view transparent before removing the expanded view
// from it. Otherwise a punch hole created by {@link android.view.SurfaceView} in the
// expanded view becomes visible on the screen. See b/126856255
mExpandedViewContainer.setAlpha(0.0f);
mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
+ if (previouslySelected != null) {
+ previouslySelected.setContentVisibility(false);
+ }
updateExpandedBubble();
updatePointerPosition();
requestUpdate();
@@ -774,6 +778,14 @@
}
if (wasExpanded) {
// Collapse the stack
+ mExpandedViewContainer.setAlpha(0.0f);
+ // TODO: In order to prevent flicker, code below should be executed after the alpha
+ // value set on the mExpandedViewContainer is reflected on the screen. However, we
+ // cannot just postpone the execution like #setSelectedBubble(), since some of member
+ // variables referred by the code are overridden before the execution.
+ if (mExpandedBubble != null) {
+ mExpandedBubble.setContentVisibility(false);
+ }
animateExpansion(false /* expand */);
logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
} else {
@@ -931,14 +943,10 @@
if (shouldExpand) {
mExpandedViewContainer.setTranslationX(xStart);
mExpandedViewContainer.setTranslationY(yStart);
- mExpandedViewContainer.setAlpha(0f);
}
mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart);
mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart);
- mExpandedViewContainer.animate()
- .setDuration(100)
- .alpha(shouldExpand ? 1f : 0f);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 3d59e66..b3b6efe 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -40,6 +40,7 @@
import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND;
@@ -3194,6 +3195,8 @@
if (newTask) {
if (r.mLaunchTaskBehind) {
transit = TRANSIT_TASK_OPEN_BEHIND;
+ } else if (getDisplay().isSingleTaskInstance()) {
+ transit = TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
} else {
// If a new task is being launched, then mark the existing top activity as
// supporting picture-in-picture while pausing only if the starting activity
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index ed56501..dbc530d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -34,7 +34,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.service.voice.IVoiceInteractionSession;
-import android.util.Pair;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
@@ -189,6 +188,13 @@
public abstract void notifyDockedStackMinimizedChanged(boolean minimized);
/**
+ * Notify listeners that contents are drawn for the first time on a single task display.
+ *
+ * @param displayId An ID of the display on which contents are drawn.
+ */
+ public abstract void notifySingleTaskDisplayDrawn(int displayId);
+
+ /**
* Start activity {@code intents} as if {@code packageName} on user {@code userId} did it.
*
* - DO NOT call it with the calling UID cleared.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 772e5e6..e9a266c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6087,7 +6087,8 @@
}
@Override
- public void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp) {
+ public void notifyAppTransitionStarting(SparseIntArray reasons,
+ long timestamp) {
synchronized (mGlobalLock) {
mStackSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
reasons, timestamp);
@@ -6095,6 +6096,11 @@
}
@Override
+ public void notifySingleTaskDisplayDrawn(int displayId) {
+ mTaskChangeNotificationController.notifySingleTaskDisplayDrawn(displayId);
+ }
+
+ @Override
public void notifyAppTransitionFinished() {
synchronized (mGlobalLock) {
mStackSupervisor.notifyAppTransitionDone();
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index ddd5c0a..19ccc62 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -29,6 +29,7 @@
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
@@ -2052,6 +2053,9 @@
case TRANSIT_CRASHING_ACTIVITY_CLOSE: {
return "TRANSIT_CRASHING_ACTIVITY_CLOSE";
}
+ case TRANSIT_SHOW_SINGLE_TASK_DISPLAY: {
+ return "TRANSIT_SHOW_SINGLE_TASK_DISPLAY";
+ }
default: {
return "<UNKNOWN: " + transition + ">";
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index d4c4e6a..6c5ef52 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -28,6 +28,7 @@
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
@@ -211,6 +212,12 @@
mService.mAtmInternal.notifyAppTransitionStarting(mTempTransitionReasons.clone(),
SystemClock.uptimeMillis());
+ if (transit == TRANSIT_SHOW_SINGLE_TASK_DISPLAY) {
+ mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
+ mService.mAtmInternal.notifySingleTaskDisplayDrawn(mDisplayContent.getDisplayId());
+ });
+ }
+
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
mDisplayContent.pendingLayoutChanges |=
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 3ec461d..d58c613 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -35,6 +35,7 @@
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
import static com.android.server.am.ActivityStackSupervisorProto.CONFIGURATION_CONTAINER;
import static com.android.server.am.ActivityStackSupervisorProto.DISPLAYS;
@@ -1217,6 +1218,15 @@
if (displayShouldSleep) {
stack.goToSleepIfPossible(false /* shuttingDown */);
} else {
+ // When the display which can only contain one task turns on, start a special
+ // transition. {@link AppTransitionController#handleAppTransitionReady} later
+ // picks up the transition, and schedules
+ // {@link ITaskStackListener#onSingleTaskDisplayDrawn} callback which is
+ // triggered after contents are drawn on the display.
+ if (display.isSingleTaskInstance()) {
+ display.mDisplayContent.prepareAppTransition(
+ TRANSIT_SHOW_SINGLE_TASK_DISPLAY, false);
+ }
stack.awakeFromSleepingLocked();
if (stack.isFocusedStackOnDisplay()
&& !mStackSupervisor.getKeyguardController()
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 66200e3..27175c7 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -54,6 +54,7 @@
private static final int NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED_MSG = 19;
private static final int NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG = 20;
private static final int NOTIFY_BACK_PRESSED_ON_TASK_ROOT = 21;
+ private static final int NOTIFY_SINGLE_TASK_DISPLAY_DRAWN = 22;
// Delay in notifying task stack change listeners (in millis)
private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -154,6 +155,10 @@
l.onSizeCompatModeActivityChanged(m.arg1, (IBinder) m.obj);
};
+ private final TaskStackConsumer mNotifySingleTaskDisplayDrawn = (l, m) -> {
+ l.onSingleTaskDisplayDrawn(m.arg1);
+ };
+
@FunctionalInterface
public interface TaskStackConsumer {
void accept(ITaskStackListener t, Message m) throws RemoteException;
@@ -233,6 +238,9 @@
case NOTIFY_BACK_PRESSED_ON_TASK_ROOT:
forAllRemoteListeners(mNotifyBackPressedOnTaskRoot, msg);
break;
+ case NOTIFY_SINGLE_TASK_DISPLAY_DRAWN:
+ forAllRemoteListeners(mNotifySingleTaskDisplayDrawn, msg);
+ break;
}
}
}
@@ -477,4 +485,14 @@
forAllLocalListeners(mNotifyBackPressedOnTaskRoot, msg);
msg.sendToTarget();
}
+
+ /**
+ * Notify listeners that contents are drawn for the first time on a single task display.
+ */
+ void notifySingleTaskDisplayDrawn(int displayId) {
+ final Message msg = mHandler.obtainMessage(NOTIFY_SINGLE_TASK_DISPLAY_DRAWN,
+ displayId, 0 /* unused */);
+ forAllLocalListeners(mNotifySingleTaskDisplayDrawn, msg);
+ msg.sendToTarget();
+ }
}
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 5136705..4c27a3c 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -49,6 +49,9 @@
<activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityRequestedOrientationChange" />
<activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityTaskChangeCallbacks" />
<activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityTaskDescriptionChange" />
+ <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityViewTestActivity" />
+ <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityInActivityView"
+ android:resizeableActivity="true" />
<activity android:name="com.android.server.wm.ScreenDecorWindowTests$TestActivity"
android:showWhenLocked="true" />
</application>
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index 62247d8..19fd93fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
@@ -26,18 +28,22 @@
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityTaskManager;
+import android.app.ActivityView;
import android.app.IActivityManager;
import android.app.ITaskStackListener;
+import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
import android.app.TaskStackListener;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.os.Bundle;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.support.test.uiautomator.UiDevice;
import android.text.TextUtils;
+import android.view.ViewGroup;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
@@ -231,6 +237,40 @@
assertTrue(activity.mOnDetachedFromWindowCalled);
}
+ @Test
+ public void testTaskOnSingleTaskDisplayDrawn() throws Exception {
+ final Instrumentation instrumentation = getInstrumentation();
+
+ final CountDownLatch activityViewReadyLatch = new CountDownLatch(1);
+ final CountDownLatch singleTaskDisplayDrawnLatch = new CountDownLatch(1);
+ registerTaskStackChangedListener(new TaskStackListener() {
+ @Override
+ public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
+ singleTaskDisplayDrawnLatch.countDown();
+ }
+ });
+ final ActivityViewTestActivity activity =
+ (ActivityViewTestActivity) startTestActivity(ActivityViewTestActivity.class);
+ final ActivityView activityView = activity.getActivityView();
+ activityView.setCallback(new ActivityView.StateCallback() {
+ @Override
+ public void onActivityViewReady(ActivityView view) {
+ activityViewReadyLatch.countDown();
+ }
+
+ @Override
+ public void onActivityViewDestroyed(ActivityView view) {
+ }
+ });
+ waitForCallback(activityViewReadyLatch);
+
+ final Context context = instrumentation.getContext();
+ Intent intent = new Intent(context, ActivityInActivityView.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ activityView.startActivity(intent);
+ waitForCallback(singleTaskDisplayDrawnLatch);
+ }
+
/**
* Starts the provided activity and returns the started instance.
*/
@@ -369,4 +409,29 @@
mOnDetachedFromWindowCountDownLatch = countDownLatch;
}
}
+
+ public static class ActivityViewTestActivity extends TestActivity {
+ private ActivityView mActivityView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mActivityView = new ActivityView(this, null /* attrs */, 0 /* defStyle */,
+ true /* singleTaskInstance */);
+ setContentView(mActivityView);
+
+ ViewGroup.LayoutParams layoutParams = mActivityView.getLayoutParams();
+ layoutParams.width = MATCH_PARENT;
+ layoutParams.height = MATCH_PARENT;
+ mActivityView.requestLayout();
+ }
+
+ ActivityView getActivityView() {
+ return mActivityView;
+ }
+ }
+
+ // Activity that has {@link android.R.attr#resizeableActivity} attribute set to {@code true}
+ public static class ActivityInActivityView extends TestActivity {}
}