From a6d6aab0e0c2cb297cda9ad6a94bbe934ba515f6 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Thu, 19 Apr 2018 18:58:22 +0200 Subject: [PATCH] Defer resizing invisible stacks while drag resizing Works around a source of jank when drag resizing in split screen mode: instead of immediately resizing the (potentially numerous) invisible secondary stacks, we defer that until the user lets go of the handle. Change-Id: I3b9faa83005fa86185d4e51b2849e3a826b7f6a9 Fixes: 78214347 Test: Open a gazillion (resizeable) tasks. Enter split screen. Drag handle, verify there is no jank Test: atest RectTest --- core/java/android/app/IActivityManager.aidl | 5 +++ core/java/android/view/IWindowManager.aidl | 6 --- .../coretests/src/android/graphics/RectTest.java | 50 +++++++++++++++++++++ graphics/java/android/graphics/Rect.java | 11 +++++ .../systemui/stackdivider/WindowManagerProxy.java | 2 +- .../android/server/am/ActivityManagerService.java | 13 ++++++ .../java/com/android/server/am/ActivityStack.java | 5 +++ .../android/server/am/ActivityStackSupervisor.java | 51 ++++++++++++++++++++++ .../android/server/wm/WindowManagerService.java | 1 - .../java/com/android/server/wm/WindowState.java | 13 +++++- 10 files changed, 148 insertions(+), 9 deletions(-) create mode 100644 core/tests/coretests/src/android/graphics/RectTest.java diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 919f71472d33..569c2bd37b6a 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -575,6 +575,11 @@ interface IActivityManager { void resizeDockedStack(in Rect dockedBounds, in Rect tempDockedTaskBounds, in Rect tempDockedTaskInsetBounds, in Rect tempOtherTaskBounds, in Rect tempOtherTaskInsetBounds); + /** + * Sets whether we are currently in an interactive split screen resize operation where we + * are changing the docked stack size. + */ + void setSplitScreenResizing(boolean resizing); int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName); // Gets the URI permissions granted to an arbitrary package (or all packages if null) // NOTE: this is different from getPersistedUriPermissions(), which returns the URIs the package diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 6486230f8e3a..8395681f0139 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -342,12 +342,6 @@ interface IWindowManager int getDockedStackSide(); /** - * Sets whether we are currently in a drag resize operation where we are changing the docked - * stack size. - */ - void setDockedStackResizing(boolean resizing); - - /** * Sets the region the user can touch the divider. This region will be excluded from the region * which is used to cause a focus switch when dispatching touch. */ diff --git a/core/tests/coretests/src/android/graphics/RectTest.java b/core/tests/coretests/src/android/graphics/RectTest.java new file mode 100644 index 000000000000..d31d7d54940c --- /dev/null +++ b/core/tests/coretests/src/android/graphics/RectTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 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 android.graphics; + +import static android.graphics.Rect.copyOrNull; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class RectTest { + + @Test + public void copyOrNull_passesThroughNull() { + assertNull(copyOrNull(null)); + } + + @Test + public void copyOrNull_copiesNonNull() { + final Rect orig = new Rect(1, 2, 3, 4); + final Rect copy = copyOrNull(orig); + + assertEquals(orig, copy); + assertNotSame(orig, copy); + } +} diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java index aff942da78d1..3843cb91154c 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -17,6 +17,7 @@ package android.graphics; import android.annotation.CheckResult; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -99,6 +100,16 @@ public final class Rect implements Parcelable { } } + /** + * Returns a copy of {@code r} if {@code r} is not {@code null}, or {@code null} otherwise. + * + * @hide + */ + @Nullable + public static Rect copyOrNull(@Nullable Rect r) { + return r == null ? null : new Rect(r); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java index 85a60624c275..1e5b37c9af50 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java @@ -180,7 +180,7 @@ public class WindowManagerProxy { @Override public void run() { try { - WindowManagerGlobal.getWindowManagerService().setDockedStackResizing(resizing); + ActivityManager.getService().setSplitScreenResizing(resizing); } catch (RemoteException e) { Log.w(TAG, "Error calling setDockedStackResizing: " + e); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e85351bfe489..bc766168d0d8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -11447,6 +11447,19 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public void setSplitScreenResizing(boolean resizing) { + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "setSplitScreenResizing()"); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (this) { + mStackSupervisor.setSplitScreenResizing(resizing); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void resizePinnedStack(Rect pinnedBounds, Rect tempPinnedTaskBounds) { enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizePinnedStack()"); final long ident = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index e20356ff12db..95bae2eb24a8 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -1743,6 +1743,11 @@ class ActivityStack extends ConfigurationContai return getDisplay().isTopStack(this); } + boolean isTopActivityVisible() { + final ActivityRecord topActivity = getTopActivity(); + return topActivity != null && topActivity.visible; + } + /** * Returns true if the stack should be visible. * diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 6a3587c69dfb..0dc2445109ea 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -43,6 +43,7 @@ import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.graphics.Rect.copyOrNull; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; @@ -245,6 +246,20 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // the activity callback indicating that it has completed pausing static final boolean PAUSE_IMMEDIATELY = true; + /** True if the docked stack is currently being resized. */ + private boolean mDockedStackResizing; + + /** + * True if there are pending docked bounds that need to be applied after + * {@link #mDockedStackResizing} is reset to false. + */ + private boolean mHasPendingDockedBounds; + private Rect mPendingDockedBounds; + private Rect mPendingTempDockedTaskBounds; + private Rect mPendingTempDockedTaskInsetBounds; + private Rect mPendingTempOtherTaskBounds; + private Rect mPendingTempOtherTaskInsetBounds; + /** * The modes which affect which tasks are returned when calling * {@link ActivityStackSupervisor#anyTaskForIdLocked(int)}. @@ -2708,6 +2723,28 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D moveTasksToFullscreenStackInSurfaceTransaction(fromStack, toDisplayId, onTop)); } + void setSplitScreenResizing(boolean resizing) { + if (resizing == mDockedStackResizing) { + return; + } + + mDockedStackResizing = resizing; + mWindowManager.setDockedStackResizing(resizing); + + if (!resizing && mHasPendingDockedBounds) { + resizeDockedStackLocked(mPendingDockedBounds, mPendingTempDockedTaskBounds, + mPendingTempDockedTaskInsetBounds, mPendingTempOtherTaskBounds, + mPendingTempOtherTaskInsetBounds, PRESERVE_WINDOWS); + + mHasPendingDockedBounds = false; + mPendingDockedBounds = null; + mPendingTempDockedTaskBounds = null; + mPendingTempDockedTaskInsetBounds = null; + mPendingTempOtherTaskBounds = null; + mPendingTempOtherTaskInsetBounds = null; + } + } + void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds, Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds, boolean preserveWindows) { @@ -2731,6 +2768,15 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return; } + if (mDockedStackResizing) { + mHasPendingDockedBounds = true; + mPendingDockedBounds = copyOrNull(dockedBounds); + mPendingTempDockedTaskBounds = copyOrNull(tempDockedTaskBounds); + mPendingTempDockedTaskInsetBounds = copyOrNull(tempDockedTaskInsetBounds); + mPendingTempOtherTaskBounds = copyOrNull(tempOtherTaskBounds); + mPendingTempOtherTaskInsetBounds = copyOrNull(tempOtherTaskInsetBounds); + } + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeDockedStack"); mWindowManager.deferSurfaceLayout(); try { @@ -2765,6 +2811,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (!current.affectedBySplitScreenResize()) { continue; } + if (mDockedStackResizing && !current.isTopActivityVisible()) { + // Non-visible stacks get resized once we're done with the resize + // interaction. + continue; + } // Need to set windowing mode here before we try to get the dock bounds. current.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); current.getStackDockedModeBounds( diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b1b026ed55cc..09e43f843983 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -6864,7 +6864,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - @Override public void setDockedStackResizing(boolean resizing) { synchronized (mWindowMap) { getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 61ce062d6047..1924327ef69c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2407,6 +2407,7 @@ class WindowState extends WindowContainer implements WindowManagerP @Override public void binderDied() { try { + boolean resetSplitScreenResizing = false; synchronized(mService.mWindowMap) { final WindowState win = mService.windowForClientLocked(mSession, mClient, false); Slog.i(TAG, "WIN DEATH: " + win); @@ -2426,13 +2427,23 @@ class WindowState extends WindowContainer implements WindowManagerP if (stack != null) { stack.resetDockedStackToMiddle(); } - mService.setDockedStackResizing(false); + resetSplitScreenResizing = true; } } else if (mHasSurface) { Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid."); WindowState.this.removeIfPossible(); } } + if (resetSplitScreenResizing) { + try { + // Note: this calls into ActivityManager, so we must *not* hold the window + // manager lock while calling this. + mService.mActivityManager.setSplitScreenResizing(false); + } catch (RemoteException e) { + // Local call, shouldn't return RemoteException. + throw e.rethrowAsRuntimeException(); + } + } } catch (IllegalArgumentException ex) { // This will happen if the window has already been removed. } -- 2.11.0