Add sync-group timeout for transition

This allows to use different timeout according to transition type.
Also
 - Avoid indeterminate timeout by scheduling for individual windows,
   or a new window joins the sync-group lately.
 - The standalone sync request from applyWithNextDraw won't be mixed
   with the control of sync-group.

Bug: 199836343
Test: atest TransitionTests#testTimeout
Test: setprop persist.debug.shell_transit 1;
      Launch an activity or change display orientation while the
      UI thread of the activity is no response, the transition
      should execute eventually within the timeout e.g. 5s and 2s.
Change-Id: Iafecc01818193504ea3fd393817691e1b5e9f084
This commit is contained in:
Riddle Hsu
2021-09-24 23:25:55 +08:00
parent 5a9ab8d69a
commit 2179ef0565
10 changed files with 103 additions and 32 deletions

View File

@@ -3642,6 +3642,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mPendingRelaunchCount > 0) {
mPendingRelaunchCount--;
if (mPendingRelaunchCount == 0 && !isClientVisible()) {
// Don't count if the client won't report drawn.
mRelaunchStartTime = 0;
}
} else {
// Update keyguard flags upon finishing relaunch.
checkKeyguardFlagsChanged();

View File

@@ -20,9 +20,11 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
import android.annotation.NonNull;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.view.SurfaceControl;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
/**
@@ -65,6 +67,7 @@ class BLASTSyncEngine {
class SyncGroup {
final int mSyncId;
final TransactionReadyListener mListener;
final Runnable mOnTimeout;
boolean mReady = false;
final ArraySet<WindowContainer> mRootMembers = new ArraySet<>();
private SurfaceControl.Transaction mOrphanTransaction = null;
@@ -72,6 +75,12 @@ class BLASTSyncEngine {
private SyncGroup(TransactionReadyListener listener, int id) {
mSyncId = id;
mListener = listener;
mOnTimeout = () -> {
Slog.w(TAG, "Sync group " + mSyncId + " timeout");
synchronized (mWm.mGlobalLock) {
onTimeout();
}
};
}
/**
@@ -114,6 +123,7 @@ class BLASTSyncEngine {
}
mListener.onTransactionReady(mSyncId, merged);
mActiveSyncs.remove(mSyncId);
mWm.mH.removeCallbacks(mOnTimeout);
}
private void setReady(boolean ready) {
@@ -136,6 +146,17 @@ class BLASTSyncEngine {
void onCancelSync(WindowContainer wc) {
mRootMembers.remove(wc);
}
private void onTimeout() {
if (!mActiveSyncs.contains(mSyncId)) return;
for (int i = mRootMembers.size() - 1; i >= 0; --i) {
final WindowContainer<?> wc = mRootMembers.valueAt(i);
if (!wc.isSyncFinished()) {
Slog.i(TAG, "Unfinished container: " + wc);
}
}
finishNow();
}
}
private final WindowManagerService mWm;
@@ -147,13 +168,23 @@ class BLASTSyncEngine {
}
int startSyncSet(TransactionReadyListener listener) {
return startSyncSet(listener, WindowState.BLAST_TIMEOUT_DURATION);
}
int startSyncSet(TransactionReadyListener listener, long timeoutMs) {
final int id = mNextSyncId++;
final SyncGroup s = new SyncGroup(listener, id);
mActiveSyncs.put(id, s);
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s", id, listener);
scheduleTimeout(s, timeoutMs);
return id;
}
@VisibleForTesting
void scheduleTimeout(SyncGroup s, long timeoutMs) {
mWm.mH.postDelayed(s.mOnTimeout, timeoutMs);
}
void addToSyncSet(int id, WindowContainer wc) {
mActiveSyncs.get(id).addToSync(wc);
}

View File

@@ -3449,7 +3449,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
public String toString() {
return "Display " + mDisplayId + " info=" + mDisplayInfo + " rootTasks=" + mChildren;
return "Display{#" + mDisplayId + " state=" + Display.stateToString(mDisplayInfo.state)
+ " size=" + mDisplayInfo.logicalWidth + "x" + mDisplayInfo.logicalHeight
+ " " + Surface.rotationToString(mDisplayInfo.rotation) + "}";
}
String getName() {

View File

@@ -165,13 +165,13 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
private boolean mNavBarAttachedToApp = false;
private int mRecentsDisplayId = INVALID_DISPLAY;
Transition(@TransitionType int type, @TransitionFlags int flags,
Transition(@TransitionType int type, @TransitionFlags int flags, long timeoutMs,
TransitionController controller, BLASTSyncEngine syncEngine) {
mType = type;
mFlags = flags;
mController = controller;
mSyncEngine = syncEngine;
mSyncId = mSyncEngine.startSyncSet(this);
mSyncId = mSyncEngine.startSyncSet(this, timeoutMs);
}
void addFlag(int flag) {

View File

@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -55,6 +56,11 @@ import java.util.function.LongConsumer;
class TransitionController {
private static final String TAG = "TransitionController";
/** The same as legacy APP_TRANSITION_TIMEOUT_MS. */
private static final int DEFAULT_TIMEOUT_MS = 5000;
/** Less duration for CHANGE type because it does not involve app startup. */
private static final int CHANGE_TIMEOUT_MS = 2000;
// State constants to line-up with legacy app-transition proto expectations.
private static final int LEGACY_STATE_IDLE = 0;
private static final int LEGACY_STATE_READY = 1;
@@ -121,7 +127,10 @@ class TransitionController {
if (mCollectingTransition != null) {
throw new IllegalStateException("Simultaneous transitions not supported yet.");
}
mCollectingTransition = new Transition(type, flags, this, mAtm.mWindowManager.mSyncEngine);
// Distinguish change type because the response time is usually expected to be not too long.
final long timeoutMs = type == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
mCollectingTransition = new Transition(type, flags, timeoutMs, this,
mAtm.mWindowManager.mSyncEngine);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s",
mCollectingTransition);
dispatchLegacyAppTransitionPending();

View File

@@ -5396,6 +5396,7 @@ public class WindowManagerService extends IWindowManager.Stub
case WINDOW_STATE_BLAST_SYNC_TIMEOUT: {
synchronized (mGlobalLock) {
final WindowState ws = (WindowState) msg.obj;
Slog.i(TAG, "Blast sync timeout: " + ws);
ws.immediatelyNotifyBlastSync();
}
break;

View File

@@ -6041,10 +6041,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// in WAITING state rather than READY.
mSyncState = SYNC_STATE_WAITING_FOR_DRAW;
requestRedrawForSync();
mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
mWmService.mH.sendNewMessageDelayed(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this,
BLAST_TIMEOUT_DURATION);
return true;
}

View File

@@ -62,7 +62,7 @@ public class SyncEngineTests extends WindowTestsBase {
public void testTrivialSyncCallback() {
TestWindowContainer mockWC = new TestWindowContainer(mWm, false /* waiter */);
BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -90,7 +90,7 @@ public class SyncEngineTests extends WindowTestsBase {
public void testWaitingSyncCallback() {
TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */);
BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -114,7 +114,7 @@ public class SyncEngineTests extends WindowTestsBase {
public void testInvisibleSyncCallback() {
TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */);
BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -142,7 +142,7 @@ public class SyncEngineTests extends WindowTestsBase {
parentWC.addChild(childWC, POSITION_TOP);
parentWC.addChild(childWC2, POSITION_TOP);
BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -175,7 +175,7 @@ public class SyncEngineTests extends WindowTestsBase {
TestWindowContainer childWC = new TestWindowContainer(mWm, true /* waiter */);
parentWC.addChild(childWC, POSITION_TOP);
BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -206,7 +206,7 @@ public class SyncEngineTests extends WindowTestsBase {
parentWC.addChild(topChildWC, POSITION_TOP);
parentWC.addChild(botChildWC, POSITION_BOTTOM);
BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -238,7 +238,7 @@ public class SyncEngineTests extends WindowTestsBase {
parentWC.addChild(topChildWC, POSITION_TOP);
parentWC.addChild(botChildWC, POSITION_BOTTOM);
BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -273,7 +273,7 @@ public class SyncEngineTests extends WindowTestsBase {
parentWC.addChild(topChildWC, POSITION_TOP);
nonMemberParentWC.addChild(botChildWC, POSITION_BOTTOM);
BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -312,7 +312,7 @@ public class SyncEngineTests extends WindowTestsBase {
parentWC.addChild(topChildWC, POSITION_TOP);
parentWC.addChild(botChildWC, POSITION_BOTTOM);
BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);

View File

@@ -21,7 +21,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -44,6 +43,7 @@ import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.SurfaceControl;
import android.window.ITaskOrganizer;
import android.window.ITransitionPlayer;
import android.window.TransitionInfo;
@@ -53,9 +53,12 @@ import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Build/Install/Run:
* atest WmTests:TransitionRecordTests
* atest WmTests:TransitionTests
*/
@SmallTest
@Presubmit
@@ -64,13 +67,13 @@ public class TransitionTests extends WindowTestsBase {
private Transition createTestTransition(int transitType) {
TransitionController controller = mock(TransitionController.class);
BLASTSyncEngine sync = new BLASTSyncEngine(mWm);
return new Transition(transitType, 0 /* flags */, controller, sync);
final BLASTSyncEngine sync = createTestBLASTSyncEngine();
return new Transition(transitType, 0 /* flags */, 0 /* timeoutMs */, controller, sync);
}
@Test
public void testCreateInfo_NewTask() {
final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
@@ -88,7 +91,7 @@ public class TransitionTests extends WindowTestsBase {
closing.mVisibleRequested = false;
opening.mVisibleRequested = true;
int transit = TRANSIT_OLD_TASK_OPEN;
final int transit = transition.mType;
int flags = 0;
// Check basic both tasks participating
@@ -127,7 +130,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testCreateInfo_NestedTasks() {
final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
@@ -152,7 +155,7 @@ public class TransitionTests extends WindowTestsBase {
opening.mVisibleRequested = true;
opening2.mVisibleRequested = true;
int transit = TRANSIT_OLD_TASK_OPEN;
final int transit = transition.mType;
int flags = 0;
// Check full promotion from leaf
@@ -177,7 +180,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testCreateInfo_DisplayArea() {
final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task showTask = createTask(mDisplayContent);
@@ -199,7 +202,7 @@ public class TransitionTests extends WindowTestsBase {
showing.mVisibleRequested = true;
showing2.mVisibleRequested = true;
int transit = TRANSIT_OLD_TASK_OPEN;
final int transit = transition.mType;
int flags = 0;
// Check promotion to DisplayArea
@@ -228,7 +231,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testCreateInfo_existenceChange() {
final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
final Transition transition = createTestTransition(TRANSIT_OPEN);
final Task openTask = createTask(mDisplayContent);
final ActivityRecord opening = createActivityRecord(openTask);
@@ -258,7 +261,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testCreateInfo_ordering() {
final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
final Transition transition = createTestTransition(TRANSIT_OPEN);
// pick some number with a high enough chance of being out-of-order when added to set.
final int taskCount = 6;
@@ -294,7 +297,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testCreateInfo_wallpaper() {
final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
final Transition transition = createTestTransition(TRANSIT_OPEN);
// pick some number with a high enough chance of being out-of-order when added to set.
final int taskCount = 4;
final int showWallpaperTask = 2;
@@ -345,7 +348,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testTargets_noIntermediatesToWallpaper() {
final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
final Transition transition = createTestTransition(TRANSIT_OPEN);
final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
@@ -419,7 +422,7 @@ public class TransitionTests extends WindowTestsBase {
openInOpen.mVisibleRequested = true;
openInChange.mVisibleRequested = true;
int transit = TRANSIT_OLD_TASK_OPEN;
final int transit = transition.mType;
int flags = 0;
// Check full promotion from leaf
@@ -449,6 +452,22 @@ public class TransitionTests extends WindowTestsBase {
info.getChange(openInChangeTask.mRemoteToken.toWindowContainerToken()), info));
}
@Test
public void testTimeout() {
final TransitionController controller = new TransitionController(mAtm,
mock(TaskSnapshotController.class));
final BLASTSyncEngine sync = new BLASTSyncEngine(mWm);
final CountDownLatch latch = new CountDownLatch(1);
// When the timeout is reached, it will finish the sync-group and notify transaction ready.
new Transition(TRANSIT_OPEN, 0 /* flags */, 10 /* timeoutMs */, controller, sync) {
@Override
public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
latch.countDown();
}
};
assertTrue(awaitInWmLock(() -> latch.await(3, TimeUnit.SECONDS)));
}
@Test
public void testIntermediateVisibility() {
final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);

View File

@@ -789,6 +789,15 @@ class WindowTestsBase extends SystemServiceTestsBase {
};
}
BLASTSyncEngine createTestBLASTSyncEngine() {
return new BLASTSyncEngine(mWm) {
@Override
void scheduleTimeout(SyncGroup s, long timeoutMs) {
// Disable timeout.
}
};
}
/**
* Avoids rotating screen disturbed by some conditions. It is usually used for the default
* display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).