Merge "Use boundsChangeTransaction to improve split-screen enter/exit" into rvc-dev

This commit is contained in:
Evan Rosky
2020-06-01 22:55:20 +00:00
committed by Android (Google) Code Review
9 changed files with 406 additions and 68 deletions

View File

@@ -209,7 +209,11 @@ public class WindowlessWindowManager implements IWindowSession {
/** @hide */
protected SurfaceControl getSurfaceControl(View rootView) {
final State s = mStateForWindow.get(rootView.getViewRootImpl().mWindow.asBinder());
final ViewRootImpl root = rootView.getViewRootImpl();
if (root == null) {
return null;
}
final State s = mStateForWindow.get(root.mWindow.asBinder());
if (s == null) {
return null;
}

View File

@@ -264,6 +264,29 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
/**
* Merges another WCT into this one.
* @param transfer When true, this will transfer everything from other potentially leaving
* other in an unusable state. When false, other is left alone, but
* SurfaceFlinger Transactions will not be merged.
* @hide
*/
public void merge(WindowContainerTransaction other, boolean transfer) {
for (int i = 0, n = other.mChanges.size(); i < n; ++i) {
final IBinder key = other.mChanges.keyAt(i);
Change existing = mChanges.get(key);
if (existing == null) {
existing = new Change();
mChanges.put(key, existing);
}
existing.merge(other.mChanges.valueAt(i), transfer);
}
for (int i = 0, n = other.mHierarchyOps.size(); i < n; ++i) {
mHierarchyOps.add(transfer ? other.mHierarchyOps.get(i)
: new HierarchyOp(other.mHierarchyOps.get(i)));
}
}
/** @hide */
public Map<IBinder, Change> getChanges() {
return mChanges;
@@ -359,6 +382,41 @@ public final class WindowContainerTransaction implements Parcelable {
mActivityWindowingMode = in.readInt();
}
/**
* @param transfer When true, this will transfer other into this leaving other in an
* undefined state. Use this if you don't intend to use other. When false,
* SurfaceFlinger Transactions will not merge.
*/
public void merge(Change other, boolean transfer) {
mConfiguration.setTo(other.mConfiguration, other.mConfigSetMask, other.mWindowSetMask);
mConfigSetMask |= other.mConfigSetMask;
mWindowSetMask |= other.mWindowSetMask;
if ((other.mChangeMask & CHANGE_FOCUSABLE) != 0) {
mFocusable = other.mFocusable;
}
if (transfer && (other.mChangeMask & CHANGE_BOUNDS_TRANSACTION) != 0) {
mBoundsChangeTransaction = other.mBoundsChangeTransaction;
other.mBoundsChangeTransaction = null;
}
if ((other.mChangeMask & CHANGE_PIP_CALLBACK) != 0) {
mPinnedBounds = transfer ? other.mPinnedBounds : new Rect(other.mPinnedBounds);
}
if ((other.mChangeMask & CHANGE_HIDDEN) != 0) {
mHidden = other.mHidden;
}
mChangeMask |= other.mChangeMask;
if (other.mActivityWindowingMode >= 0) {
mActivityWindowingMode = other.mActivityWindowingMode;
}
if (other.mWindowingMode >= 0) {
mWindowingMode = other.mWindowingMode;
}
if (other.mBoundsChangeSurfaceBounds != null) {
mBoundsChangeSurfaceBounds = transfer ? other.mBoundsChangeSurfaceBounds
: new Rect(other.mBoundsChangeSurfaceBounds);
}
}
public int getWindowingMode() {
return mWindowingMode;
}
@@ -522,6 +580,12 @@ public final class WindowContainerTransaction implements Parcelable {
mToTop = toTop;
}
public HierarchyOp(@NonNull HierarchyOp copy) {
mContainer = copy.mContainer;
mReparent = copy.mReparent;
mToTop = copy.mToTop;
}
protected HierarchyOp(Parcel in) {
mContainer = in.readStrongBinder();
mReparent = in.readStrongBinder();

View File

@@ -99,16 +99,19 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
private Handler mHandler;
private KeyguardStateController mKeyguardStateController;
private WindowManagerProxy mWindowManagerProxy;
private final ArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners =
new ArrayList<>();
private SplitScreenTaskOrganizer mSplits = new SplitScreenTaskOrganizer(this);
private DisplayChangeController.OnDisplayChangingListener mRotationController =
(display, fromRotation, toRotation, t) -> {
if (!mSplits.isSplitScreenSupported()) {
(display, fromRotation, toRotation, wct) -> {
if (!mSplits.isSplitScreenSupported() || mWindowManagerProxy == null) {
return;
}
WindowContainerTransaction t = new WindowContainerTransaction();
DisplayLayout displayLayout =
new DisplayLayout(mDisplayController.getDisplayLayout(display));
SplitDisplayLayout sdl = new SplitDisplayLayout(mContext, displayLayout, mSplits);
@@ -127,6 +130,17 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
if (isSplitActive()) {
WindowManagerProxy.applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t);
}
if (mWindowManagerProxy.queueSyncTransactionIfWaiting(t)) {
// Because sync transactions are serialized, its possible for an "older"
// bounds-change to get applied after a screen rotation. In that case, we
// want to actually defer on that rather than apply immediately. Of course,
// this means that the bounds may not change until after the rotation so
// the user might see some artifacts. This should be rare.
Slog.w(TAG, "Screen rotated while other operations were pending, this may"
+ " result in some graphical artifacts.");
} else {
wct.merge(t, true /* transfer */);
}
};
private final DividerImeController mImePositionProcessor;
@@ -159,6 +173,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
mRecentsOptionalLazy = recentsOptionalLazy;
mForcedResizableController = new ForcedResizableInfoActivityController(context, this);
mTransactionPool = transactionPool;
mWindowManagerProxy = new WindowManagerProxy(mTransactionPool, mHandler);
mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler);
}
@@ -278,9 +293,9 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
mView.injectDependencies(mWindowManager, mDividerState, this, mSplits, mSplitLayout,
mImePositionProcessor);
mImePositionProcessor, mWindowManagerProxy);
mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, null /* transaction */);
final int size = dctx.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_thickness);
final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
@@ -303,7 +318,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
addDivider(configuration);
if (mMinimized) {
mView.setMinimizedDockStack(true, mHomeStackResizable);
mView.setMinimizedDockStack(true, mHomeStackResizable, null /* transaction */);
updateTouchable();
}
mView.setHidden(isDividerHidden);
@@ -327,11 +342,13 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
if (visible) {
mView.enterSplitMode(mHomeStackResizable);
// Update state because animations won't finish.
mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
mWindowManagerProxy.runInSync(
t -> mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, t));
} else {
mView.exitSplitMode();
// un-minimize so that next entry triggers minimize anim.
mView.setMinimizedDockStack(false /* minimized */, mHomeStackResizable);
mWindowManagerProxy.runInSync(
t -> mView.setMinimizedDockStack(false, mHomeStackResizable, t));
}
// Notify existence listeners
synchronized (mDockedStackExistsListeners) {
@@ -344,12 +361,6 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
}
}
void onSplitDismissed() {
updateVisibility(false /* visible */);
mMinimized = false;
removeDivider();
}
/** Switch to minimized state if appropriate */
public void setMinimized(final boolean minimized) {
if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
@@ -405,7 +416,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
}
}
updateTouchable();
WindowOrganizer.applyTransaction(wct);
mWindowManagerProxy.applySyncTransaction(wct);
}
void setAdjustedForIme(boolean adjustedForIme) {
@@ -501,7 +512,14 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
update(mDisplayController.getDisplayContext(
mContext.getDisplayId()).getResources().getConfiguration());
// Set resizable directly here because applyEnterSplit already resizes home stack.
mHomeStackResizable = WindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
}
void startDismissSplit() {
mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, true /* dismissOrMaximize */);
updateVisibility(false /* visible */);
mMinimized = false;
removeDivider();
}
void ensureMinimizedSplit() {
@@ -530,6 +548,10 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
return mSplitLayout;
}
WindowManagerProxy getWmProxy() {
return mWindowManagerProxy;
}
/** @return the container token for the secondary split root task. */
public WindowContainerToken getSecondaryRoot() {
if (mSplits == null || mSplits.mSecondary == null) {

View File

@@ -29,7 +29,6 @@ import android.view.SurfaceControl;
import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowOrganizer;
import androidx.annotation.Nullable;
@@ -213,7 +212,7 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor
SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
}
WindowOrganizer.applyTransaction(wct);
mSplits.mDivider.getWmProxy().applySyncTransaction(wct);
// Update all the adjusted-for-ime states
if (!mPaused) {

View File

@@ -46,6 +46,7 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
import android.view.WindowManager;
@@ -138,7 +139,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
private final Rect mOtherInsetRect = new Rect();
private final Rect mLastResizeRect = new Rect();
private final Rect mTmpRect = new Rect();
private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance();
private WindowManagerProxy mWindowManagerProxy;
private DividerWindowManager mWindowManager;
private VelocityTracker mVelocityTracker;
private FlingAnimationUtils mFlingAnimationUtils;
@@ -360,13 +361,14 @@ public class DividerView extends FrameLayout implements OnTouchListener,
public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState,
DividerCallbacks callback, SplitScreenTaskOrganizer tiles, SplitDisplayLayout sdl,
DividerImeController imeController) {
DividerImeController imeController, WindowManagerProxy wmProxy) {
mWindowManager = windowManager;
mState = dividerState;
mCallback = callback;
mTiles = tiles;
mSplitLayout = sdl;
mImeController = imeController;
mWindowManagerProxy = wmProxy;
if (mState.mRatioPositionBeforeMinimized == 0) {
// Set the middle target as the initial state
@@ -376,10 +378,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
}
}
public WindowManagerProxy getWindowManagerProxy() {
return mWindowManagerProxy;
}
public Rect getNonMinimizedSplitScreenSecondaryBounds() {
mOtherTaskRect.set(mSplitLayout.mSecondary);
return mOtherTaskRect;
@@ -519,7 +517,8 @@ public class DividerView extends FrameLayout implements OnTouchListener,
if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
SnapTarget snapTarget = getSnapAlgorithm().calculateSnapTarget(
mStartPosition, 0 /* velocity */, false /* hardDismiss */);
resizeStackSurfaces(calculatePosition(x, y), mStartPosition, snapTarget);
resizeStackSurfaces(calculatePosition(x, y), mStartPosition, snapTarget,
null /* transaction */);
}
break;
case MotionEvent.ACTION_UP:
@@ -608,7 +607,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f
? TASK_POSITION_SAME
: snapTarget.taskPosition,
snapTarget));
snapTarget, null /* transaction */));
Consumer<Boolean> endAction = cancelled -> {
if (DEBUG) Slog.d(TAG, "End Fling " + cancelled + " min:" + mIsInMinimizeInteraction);
final boolean wasMinimizeInteraction = mIsInMinimizeInteraction;
@@ -716,7 +715,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
|| mDockSide == WindowManager.DOCKED_BOTTOM;
}
mWindowManagerProxy.dismissOrMaximizeDocked(mTiles, dismissOrMaximize);
mWindowManagerProxy.dismissOrMaximizeDocked(mTiles, mSplitLayout, dismissOrMaximize);
Transaction t = mTiles.getTransaction();
setResizeDimLayer(t, true /* primary */, 0f);
setResizeDimLayer(t, false /* primary */, 0f);
@@ -806,7 +805,8 @@ public class DividerView extends FrameLayout implements OnTouchListener,
mWindowManager.setTouchRegion(touchRegion);
}
public void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable) {
void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable,
Transaction t) {
mHomeStackResizable = isHomeStackResizable;
updateDockSide();
if (!minimized) {
@@ -840,9 +840,10 @@ public class DividerView extends FrameLayout implements OnTouchListener,
// Relayout to recalculate the divider shadow when minimizing
requestLayout();
mIsInMinimizeInteraction = true;
resizeStackSurfaces(mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget());
resizeStackSurfaces(
mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget(), t);
} else {
resizeStackSurfaces(mSnapTargetBeforeMinimized);
resizeStackSurfaces(mSnapTargetBeforeMinimized, t);
mIsInMinimizeInteraction = false;
}
}
@@ -873,10 +874,11 @@ public class DividerView extends FrameLayout implements OnTouchListener,
* assigned to it.
*/
private SurfaceControl getWindowSurfaceControl() {
if (getViewRootImpl() == null) {
final ViewRootImpl root = getViewRootImpl();
if (root == null) {
return null;
}
SurfaceControl out = getViewRootImpl().getSurfaceControl();
SurfaceControl out = root.getSurfaceControl();
if (out != null && out.isValid()) {
return out;
}
@@ -885,15 +887,13 @@ public class DividerView extends FrameLayout implements OnTouchListener,
void exitSplitMode() {
// Reset tile bounds
post(() -> {
final SurfaceControl sc = getWindowSurfaceControl();
if (sc == null) {
return;
}
Transaction t = mTiles.getTransaction();
t.hide(sc).apply();
mTiles.releaseTransaction(t);
});
final SurfaceControl sc = getWindowSurfaceControl();
if (sc == null) {
return;
}
Transaction t = mTiles.getTransaction();
t.hide(sc).apply();
mTiles.releaseTransaction(t);
int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
WindowManagerProxy.applyResizeSplits(midPos, mSplitLayout);
}
@@ -1049,8 +1049,8 @@ public class DividerView extends FrameLayout implements OnTouchListener,
mDividerSize);
}
private void resizeStackSurfaces(SnapTarget taskSnapTarget) {
resizeStackSurfaces(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget);
private void resizeStackSurfaces(SnapTarget taskSnapTarget, Transaction t) {
resizeStackSurfaces(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget, t);
}
void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect otherRect) {
@@ -1105,7 +1105,8 @@ public class DividerView extends FrameLayout implements OnTouchListener,
}
}
void resizeStackSurfaces(int position, int taskPosition, SnapTarget taskSnapTarget) {
void resizeStackSurfaces(int position, int taskPosition, SnapTarget taskSnapTarget,
Transaction transaction) {
if (mRemoved) {
// This divider view has been removed so shouldn't have any additional influence.
return;
@@ -1123,7 +1124,8 @@ public class DividerView extends FrameLayout implements OnTouchListener,
mBackground.invalidate();
}
Transaction t = mTiles.getTransaction();
final boolean ownTransaction = transaction == null;
final Transaction t = ownTransaction ? mTiles.getTransaction() : transaction;
mLastResizeRect.set(mDockedRect);
if (mHomeStackResizable && mIsInMinimizeInteraction) {
calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, mDockSide,
@@ -1138,8 +1140,10 @@ public class DividerView extends FrameLayout implements OnTouchListener,
}
resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect,
mOtherTaskRect);
t.apply();
mTiles.releaseTransaction(t);
if (ownTransaction) {
t.apply();
mTiles.releaseTransaction(t);
}
return;
}
@@ -1201,8 +1205,10 @@ public class DividerView extends FrameLayout implements OnTouchListener,
SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
float dimFraction = getDimFraction(position, closestDismissTarget);
setResizeDimLayer(t, isDismissTargetPrimary(closestDismissTarget), dimFraction);
t.apply();
mTiles.releaseTransaction(t);
if (ownTransaction) {
t.apply();
mTiles.releaseTransaction(t);
}
}
private void applyExitAnimationParallax(Rect taskRect, int position) {
@@ -1383,7 +1389,8 @@ public class DividerView extends FrameLayout implements OnTouchListener,
resizeStackSurfaces(calculatePositionForInsetBounds(),
mSplitLayout.getSnapAlgorithm().getMiddleTarget().position,
mSplitLayout.getSnapAlgorithm().getMiddleTarget());
mSplitLayout.getSnapAlgorithm().getMiddleTarget(),
null /* transaction */);
}
void onRecentsDrawn() {

View File

@@ -200,8 +200,7 @@ class SplitScreenTaskOrganizer extends TaskOrganizer {
Log.d(TAG, " was in split, so this means leave it "
+ mPrimary.topActivityType + " " + mSecondary.topActivityType);
}
WindowManagerProxy.applyDismissSplit(this, true /* dismissOrMaximize */);
mDivider.onSplitDismissed();
mDivider.startDismissSplit();
} else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) {
// Wasn't in split-mode (both were empty), but now that the primary split is
// populated, we should fully enter split by moving everything else into secondary.

View File

@@ -0,0 +1,177 @@
/*
* Copyright (C) 2020 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.stackdivider;
import android.os.Handler;
import android.util.Slog;
import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import android.window.WindowOrganizer;
import androidx.annotation.NonNull;
import com.android.systemui.TransactionPool;
import java.util.ArrayList;
/**
* Helper for serializing sync-transactions and corresponding callbacks.
*/
class SyncTransactionQueue {
private static final boolean DEBUG = Divider.DEBUG;
private static final String TAG = "SyncTransactionQueue";
// Just a little longer than the sync-engine timeout of 5s
private static final int REPLY_TIMEOUT = 5300;
private final TransactionPool mTransactionPool;
private final Handler mHandler;
// Sync Transactions currently don't support nesting or interleaving properly, so
// queue up transactions to run them serially.
private final ArrayList<SyncCallback> mQueue = new ArrayList<>();
private SyncCallback mInFlight = null;
private final ArrayList<TransactionRunnable> mRunnables = new ArrayList<>();
private final Runnable mOnReplyTimeout = () -> {
synchronized (mQueue) {
if (mInFlight != null && mQueue.contains(mInFlight)) {
Slog.w(TAG, "Sync Transaction timed-out: " + mInFlight.mWCT);
mInFlight.onTransactionReady(mInFlight.mId, new SurfaceControl.Transaction());
}
}
};
SyncTransactionQueue(TransactionPool pool, Handler handler) {
mTransactionPool = pool;
mHandler = handler;
}
/**
* Queues a sync transaction to be sent serially to WM.
*/
void queue(WindowContainerTransaction wct) {
SyncCallback cb = new SyncCallback(wct);
synchronized (mQueue) {
if (DEBUG) Slog.d(TAG, "Queueing up " + wct);
mQueue.add(cb);
if (mQueue.size() == 1) {
cb.send();
}
}
}
/**
* Queues a sync transaction only if there are already sync transaction(s) queued or in flight.
* Otherwise just returns without queueing.
* @return {@code true} if queued, {@code false} if not.
*/
boolean queueIfWaiting(WindowContainerTransaction wct) {
synchronized (mQueue) {
if (mQueue.isEmpty()) {
if (DEBUG) Slog.d(TAG, "Nothing in queue, so skip queueing up " + wct);
return false;
}
if (DEBUG) Slog.d(TAG, "Queue is non-empty, so queueing up " + wct);
SyncCallback cb = new SyncCallback(wct);
mQueue.add(cb);
if (mQueue.size() == 1) {
cb.send();
}
}
return true;
}
/**
* Runs a runnable in sync with sync transactions (ie. when the current in-flight transaction
* returns. If there are no transactions in-flight, runnable executes immediately.
*/
void runInSync(TransactionRunnable runnable) {
synchronized (mQueue) {
if (DEBUG) Slog.d(TAG, "Run in sync. mInFlight=" + mInFlight);
if (mInFlight != null) {
mRunnables.add(runnable);
return;
}
}
SurfaceControl.Transaction t = mTransactionPool.acquire();
runnable.runWithTransaction(t);
t.apply();
mTransactionPool.release(t);
}
// Synchronized on mQueue
private void onTransactionReceived(@NonNull SurfaceControl.Transaction t) {
if (DEBUG) Slog.d(TAG, " Running " + mRunnables.size() + " sync runnables");
for (int i = 0, n = mRunnables.size(); i < n; ++i) {
mRunnables.get(i).runWithTransaction(t);
}
mRunnables.clear();
t.apply();
t.close();
}
interface TransactionRunnable {
void runWithTransaction(SurfaceControl.Transaction t);
}
private class SyncCallback extends WindowContainerTransactionCallback {
int mId = -1;
final WindowContainerTransaction mWCT;
SyncCallback(WindowContainerTransaction wct) {
mWCT = wct;
}
// Must be sychronized on mQueue
void send() {
if (mInFlight != null) {
throw new IllegalStateException("Sync Transactions must be serialized. In Flight: "
+ mInFlight.mId + " - " + mInFlight.mWCT);
}
mInFlight = this;
if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT);
mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
if (DEBUG) Slog.d(TAG, " Sent sync transaction. Got id=" + mId);
mHandler.postDelayed(mOnReplyTimeout, REPLY_TIMEOUT);
}
@Override
public void onTransactionReady(int id,
@androidx.annotation.NonNull SurfaceControl.Transaction t) {
mHandler.post(() -> {
synchronized (mQueue) {
if (mId != id) {
Slog.e(TAG, "Got an unexpected onTransactionReady. Expected "
+ mId + " but got " + id);
return;
}
mInFlight = null;
mHandler.removeCallbacks(mOnReplyTimeout);
if (DEBUG) Slog.d(TAG, "onTransactionReady id=" + mId);
mQueue.remove(this);
onTransactionReceived(t);
if (!mQueue.isEmpty()) {
mQueue.get(0).send();
}
}
});
}
}
}

View File

@@ -25,9 +25,11 @@ import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowManagerGlobal;
import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
@@ -35,6 +37,7 @@ import android.window.WindowContainerTransaction;
import android.window.WindowOrganizer;
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.TransactionPool;
import java.util.ArrayList;
import java.util.List;
@@ -49,8 +52,6 @@ public class WindowManagerProxy {
private static final String TAG = "WindowManagerProxy";
private static final int[] HOME_AND_RECENTS = {ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS};
private static final WindowManagerProxy sInstance = new WindowManagerProxy();
@GuardedBy("mDockedRect")
private final Rect mDockedRect = new Rect();
@@ -61,6 +62,8 @@ public class WindowManagerProxy {
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private final SyncTransactionQueue mSyncTransactionQueue;
private final Runnable mSetTouchableRegionRunnable = new Runnable() {
@Override
public void run() {
@@ -76,16 +79,13 @@ public class WindowManagerProxy {
}
};
private WindowManagerProxy() {
WindowManagerProxy(TransactionPool transactionPool, Handler handler) {
mSyncTransactionQueue = new SyncTransactionQueue(transactionPool, handler);
}
public static WindowManagerProxy getInstance() {
return sInstance;
}
void dismissOrMaximizeDocked(
final SplitScreenTaskOrganizer tiles, final boolean dismissOrMaximize) {
mExecutor.execute(() -> applyDismissSplit(tiles, dismissOrMaximize));
void dismissOrMaximizeDocked(final SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout,
final boolean dismissOrMaximize) {
mExecutor.execute(() -> applyDismissSplit(tiles, layout, dismissOrMaximize));
}
public void setResizing(final boolean resizing) {
@@ -163,7 +163,7 @@ public class WindowManagerProxy {
*
* @return whether the home stack is resizable
*/
static boolean applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout) {
boolean applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout) {
// Set launchtile first so that any stack created after
// getAllStackInfos and before reparent (even if unlikely) are placed
// correctly.
@@ -174,6 +174,8 @@ public class WindowManagerProxy {
if (rootTasks.isEmpty()) {
return false;
}
ActivityManager.RunningTaskInfo topHomeTask = null;
boolean homeIsTop = false;
for (int i = rootTasks.size() - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i);
// Only move resizeable task to split secondary. WM will just ignore this anyways...
@@ -183,16 +185,25 @@ public class WindowManagerProxy {
!= WINDOWING_MODE_FULLSCREEN) {
continue;
}
// Since this iterates from bottom to top, update topHomeTask for every fullscreen task
// so it will be left with the status of the top one.
topHomeTask = isHomeOrRecentTask(rootTask) ? rootTask : null;
wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
}
// Move the secondary split-forward.
wct.reorder(tiles.mSecondary.token, true /* onTop */);
boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct);
WindowOrganizer.applyTransaction(wct);
if (isHomeResizable && topHomeTask != null) {
// Translate/update-crop of secondary out-of-band with sync transaction -- Until BALST
// is enabled, this temporarily syncs the home surface position with offset until
// sync transaction finishes.
wct.setBoundsChangeTransaction(topHomeTask.token, tiles.mHomeBounds);
}
applySyncTransaction(wct);
return isHomeResizable;
}
private static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
final int atype = ti.configuration.windowConfiguration.getActivityType();
return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS;
}
@@ -203,7 +214,8 @@ public class WindowManagerProxy {
* split (thus resulting in the top of the secondary split becoming
* fullscreen. {@code false} resolves the other way.
*/
static void applyDismissSplit(SplitScreenTaskOrganizer tiles, boolean dismissOrMaximize) {
void applyDismissSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout,
boolean dismissOrMaximize) {
// Set launch root first so that any task created after getChildContainers and
// before reparent (pretty unlikely) are put into fullscreen.
TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
@@ -229,6 +241,7 @@ public class WindowManagerProxy {
wct.reparent(primaryChildren.get(i).token, null /* parent */,
true /* onTop */);
}
boolean homeOnTop = false;
// Don't need to worry about home tasks because they are already in the "proper"
// order within the secondary split.
for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
@@ -236,8 +249,31 @@ public class WindowManagerProxy {
wct.reparent(ti.token, null /* parent */, true /* onTop */);
if (isHomeOrRecentTask(ti)) {
wct.setBounds(ti.token, null);
if (i == 0) {
homeOnTop = true;
}
}
}
if (homeOnTop) {
// Translate/update-crop of secondary out-of-band with sync transaction -- instead
// play this in sync with new home-app frame because until BALST is enabled this
// shows up on screen before the syncTransaction returns.
// We only have access to the secondary root surface, though, so in order to
// position things properly, we have to take into account the existing negative
// offset/crop of the minimized-home task.
final boolean landscape = layout.mDisplayLayout.isLandscape();
final int posX = landscape ? layout.mSecondary.left - tiles.mHomeBounds.left
: layout.mSecondary.left;
final int posY = landscape ? layout.mSecondary.top
: layout.mSecondary.top - tiles.mHomeBounds.top;
final SurfaceControl.Transaction sft = new SurfaceControl.Transaction();
sft.setPosition(tiles.mSecondarySurface, posX, posY);
final Rect crop = new Rect(0, 0, layout.mDisplayLayout.width(),
layout.mDisplayLayout.height());
crop.offset(-posX, -posY);
sft.setWindowCrop(tiles.mSecondarySurface, crop);
wct.setBoundsChangeTransaction(tiles.mSecondary.token, sft);
}
} else {
// Maximize, so move non-home secondary split first
for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
@@ -267,6 +303,29 @@ public class WindowManagerProxy {
}
// Reset focusable to true
wct.setFocusable(tiles.mPrimary.token, true /* focusable */);
WindowOrganizer.applyTransaction(wct);
applySyncTransaction(wct);
}
/**
* Utility to apply a sync transaction serially with other sync transactions.
*
* @see SyncTransactionQueue#queue
*/
void applySyncTransaction(WindowContainerTransaction wct) {
mSyncTransactionQueue.queue(wct);
}
/**
* @see SyncTransactionQueue#queueIfWaiting
*/
boolean queueSyncTransactionIfWaiting(WindowContainerTransaction wct) {
return mSyncTransactionQueue.queueIfWaiting(wct);
}
/**
* @see SyncTransactionQueue#runInSync
*/
void runInSync(SyncTransactionQueue.TransactionRunnable runnable) {
mSyncTransactionQueue.runInSync(runnable);
}
}

View File

@@ -21,6 +21,7 @@ import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.os.PowerManager.DRAW_WAKE_LOCK;
@@ -3757,6 +3758,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()) {
return false;
}
// TODO(157912944): formalize drag-resizing so that exceptions aren't hardcoded like this
if (task.getActivityType() == ACTIVITY_TYPE_HOME) {
// The current sys-ui implementations never live-resize home, so to prevent WSA from
// creating/destroying surfaces (which messes up sync-transactions), skip HOME tasks.
return false;
}
if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
// Floating windows never enter drag resize mode.
return false;