diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index 4771f9f6ad049..3bf659b663b0f 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -120,6 +120,7 @@ public class ActivityView extends ViewGroup { mActivityTaskManager = ActivityTaskManager.getService(); mSurfaceView = new SurfaceView(context); + mSurfaceView.setAlpha(0f); mSurfaceCallback = new SurfaceCallback(); mSurfaceView.getHolder().addCallback(mSurfaceCallback); addView(mSurfaceView); @@ -346,6 +347,16 @@ public class ActivityView extends ViewGroup { mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */); } + @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 diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl index 1fdc8ca5b2918..3f6880fc26258 100644 --- a/core/java/android/app/ITaskStackListener.aidl +++ b/core/java/android/app/ITaskStackListener.aidl @@ -169,4 +169,12 @@ oneway interface ITaskStackListener { * @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 00f3ad58afa6d..36daf32234dba 100644 --- a/core/java/android/app/TaskStackListener.java +++ b/core/java/android/app/TaskStackListener.java @@ -173,4 +173,8 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { 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 254d04e8715de..add7376b85311 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -27,6 +27,7 @@ import android.content.res.Configuration; 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 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb private SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction(); + /** + * A callback which reflects an alpha value of this view onto the underlying surfaces. + * + *

Note: 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.

+ */ + 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 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb 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 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb } 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 a25f2eede9052..1f89de8611119 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -260,6 +260,13 @@ public interface WindowManager extends ViewManager { */ 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 */ @@ -287,7 +294,8 @@ public interface WindowManager extends ViewManager { 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 21b3a00823196..bd2b19c0ddfe3 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 @@ public abstract class TaskStackChangeListener { 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 06ae399a5e4ef..c89f2ab7f172c 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 @@ public class TaskStackChangeListeners extends TaskStackListener { } @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 @@ public class TaskStackChangeListeners extends TaskStackListener { 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 @@ public class TaskStackChangeListeners extends TaskStackListener { } 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 f60e95e326004..5c6c397229002 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 @@ class Bubble { 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 @@ -167,6 +183,13 @@ class Bubble { return mLastAccessed; } + /** + * @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). */ diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index dcc0419ab0cf3..c2f4cfffdc6a4 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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 e7948b5149a0b..a4a2d9e772a54 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 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */, true /* singleTaskInstance */); + + setContentVisibility(false); addView(mActivityView); // Expanded stack layout, top to bottom: @@ -238,6 +240,22 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } } + /** + * 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 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList parent.removeView(mNotifRow); } addView(mNotifRow, 1 /* index */); + mPointerView.setAlpha(1f); } } @@ -336,12 +355,12 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList 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 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList 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 bec90d2edad14..14d8f3786f70a 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 @@ public class BubbleStackView extends FrameLayout { } 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 @@ public class BubbleStackView extends FrameLayout { } 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 @@ public class BubbleStackView extends FrameLayout { 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 3d59e66d13efd..b3b6efed1a375 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_CLOSE; 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 @@ class ActivityStack extends ConfigurationContainer { 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 ed56501f4351e..dbc530d38684d 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.IBinder; 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; @@ -188,6 +187,13 @@ public abstract class ActivityTaskManagerInternal { */ 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. * diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 772e5e646825e..e9a266cfb924a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6087,13 +6087,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp) { + public void notifyAppTransitionStarting(SparseIntArray reasons, + long timestamp) { synchronized (mGlobalLock) { mStackSupervisor.getActivityMetricsLogger().notifyTransitionStarting( reasons, timestamp); } } + @Override + public void notifySingleTaskDisplayDrawn(int displayId) { + mTaskChangeNotificationController.notifySingleTaskDisplayDrawn(displayId); + } + @Override public void notifyAppTransitionFinished() { synchronized (mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index ddd5c0aa7dc02..19ccc62527e91 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_GOING_AWAY_ON_WALLPAPE 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 @@ public class AppTransition implements Dump { case TRANSIT_CRASHING_ACTIVITY_CLOSE: { return "TRANSIT_CRASHING_ACTIVITY_CLOSE"; } + case TRANSIT_SHOW_SINGLE_TASK_DISPLAY: { + return "TRANSIT_SHOW_SINGLE_TASK_DISPLAY"; + } default: { return ""; } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index d4c4e6a93fa65..6c5ef52592851 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_FLAG_KEYGUARD_GOING_AWAY_WITH_W 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 @@ public class AppTransitionController { 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 3ec461d065ffa..d58c61368f9a7 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.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; 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 @@ class RootActivityContainer extends ConfigurationContainer 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 66200e39938bb..27175c7068126 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 @@ class TaskChangeNotificationController { 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 @@ class TaskChangeNotificationController { 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 @@ class TaskChangeNotificationController { 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 @@ class TaskChangeNotificationController { 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 5136705ffbc25..4c27a3ce0bf3a 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -49,6 +49,9 @@ + + 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 62247d8894857..19fd93fee5f03 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.Activity; 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 @@ public class TaskStackChangedListenerTest { 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 @@ public class TaskStackChangedListenerTest { 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 {} }