Fix showing black background when resizing the divider bar.

Reports resizing state to WM core so that app client could apply bigger
visible bounds while resizing splits. Also moves DividerHandleView to
more generic package.

Fix: 172704238
Test: manul check divider behavior
Test: atest WMShellUnitTests
Change-Id: I1656c86de9a0b5dfe5e6da57ccc9d5f6e467843a
This commit is contained in:
Jerry Chang
2020-12-04 16:11:15 +08:00
parent 519fb38710
commit 2c8ea7b021
10 changed files with 116 additions and 58 deletions

View File

@@ -27,9 +27,9 @@
<com.android.wm.shell.legacysplitscreen.MinimizedDockShadow
style="@style/DockedDividerMinimizedShadow"
android:id="@+id/minimized_dock_shadow"
android:alpha="0"/>">
android:alpha="0"/>
<com.android.wm.shell.legacysplitscreen.DividerHandleView
<com.android.wm.shell.common.split.DividerHandleView
style="@style/DockedDividerHandle"
android:id="@+id/docked_divider_handle"
android:contentDescription="@string/accessibility_divider"

View File

@@ -24,4 +24,10 @@
android:id="@+id/docked_divider_background"
android:background="@color/docked_divider_background"/>
<com.android.wm.shell.common.split.DividerHandleView
style="@style/DockedDividerHandle"
android:id="@+id/docked_divider_handle"
android:contentDescription="@string/accessibility_divider"
android:background="@null"/>
</com.android.wm.shell.common.split.DividerView>

View File

@@ -96,7 +96,8 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan
mTaskInfo2 = task2;
mSplitLayout = new SplitLayout(
mDisplayController.getDisplayContext(mRootTaskInfo.displayId),
mRootTaskInfo.configuration, this, b -> b.setParent(mRootTaskLeash));
mRootTaskInfo.configuration, this /* layoutChangeListener */,
b -> b.setParent(mRootTaskLeash));
final WindowContainerToken token1 = task1.token;
final WindowContainerToken token2 = task2.token;
@@ -191,22 +192,7 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan
if (mSplitLayout != null
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) {
// Update bounds when root bounds or its orientation changed.
final WindowContainerTransaction wct = new WindowContainerTransaction();
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
final Rect dividerBounds = mSplitLayout.getDividerBounds();
final Rect bounds1 = mSplitLayout.getBounds1();
final Rect bounds2 = mSplitLayout.getBounds2();
wct.setBounds(mTaskInfo1.token, bounds1)
.setBounds(mTaskInfo2.token, bounds2);
mController.getTaskOrganizer().applyTransaction(wct);
mSyncQueue.runInSync(t -> t
.setPosition(mTaskLeash1, bounds1.left, bounds1.top)
.setPosition(mTaskLeash2, bounds2.left, bounds2.top)
.setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
// Resets layer to divider bar to make sure it is always on top.
.setLayer(dividerLeash, Integer.MAX_VALUE));
onBoundsChanged(mSplitLayout);
}
} else if (taskInfo.taskId == getTaskId1()) {
mTaskInfo1 = taskInfo;
@@ -262,6 +248,10 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan
final Rect bounds1 = layout.getBounds1();
final Rect bounds2 = layout.getBounds2();
mSyncQueue.runInSync(t -> t
// Ignores the original surface bounds so that the app could fill up the gap
// between each surface with corresponding background while resizing.
.setWindowCrop(mTaskLeash1, bounds1.width(), bounds1.height())
.setWindowCrop(mTaskLeash2, bounds2.width(), bounds2.height())
.setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
.setPosition(mTaskLeash1, bounds1.left, bounds1.top)
.setPosition(mTaskLeash2, bounds2.left, bounds2.top));
@@ -279,6 +269,10 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan
.setBounds(mTaskInfo2.token, bounds2);
mController.getTaskOrganizer().applyTransaction(wct);
mSyncQueue.runInSync(t -> t
// Resets layer of divider bar to make sure it is always on top.
.setLayer(dividerLeash, Integer.MAX_VALUE)
.setWindowCrop(mTaskLeash1, bounds1.width(), bounds1.height())
.setWindowCrop(mTaskLeash2, bounds2.width(), bounds2.height())
.setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
.setPosition(mTaskLeash1, bounds1.left, bounds1.top)
.setPosition(mTaskLeash2, bounds2.left, bounds2.top));

View File

@@ -14,7 +14,10 @@
* limitations under the License.
*/
package com.android.wm.shell.legacysplitscreen;
package com.android.wm.shell.common.split;
import static com.android.wm.shell.common.split.DividerView.TOUCH_ANIMATION_DURATION;
import static com.android.wm.shell.common.split.DividerView.TOUCH_RELEASE_ANIMATION_DURATION;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -84,7 +87,8 @@ public class DividerHandleView extends View {
mCircleDiameter = (mWidth + mHeight) / 3;
}
void setTouching(boolean touching, boolean animate) {
/** Sets touching state for this handle view. */
public void setTouching(boolean touching, boolean animate) {
if (touching == mTouching) {
return;
}
@@ -116,8 +120,8 @@ public class DividerHandleView extends View {
mAnimator = new AnimatorSet();
mAnimator.playTogether(widthAnimator, heightAnimator);
mAnimator.setDuration(touching
? DividerView.TOUCH_ANIMATION_DURATION
: DividerView.TOUCH_RELEASE_ANIMATION_DURATION);
? TOUCH_ANIMATION_DURATION
: TOUCH_RELEASE_ANIMATION_DURATION);
mAnimator.setInterpolator(touching
? Interpolators.TOUCH_RESPONSE
: Interpolators.FAST_OUT_SLOW_IN);

View File

@@ -33,17 +33,23 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
/**
* Stack divider for app pair.
*/
// TODO(b/172704238): add handle view to indicate touching status.
public class DividerView extends FrameLayout implements View.OnTouchListener {
public static final long TOUCH_ANIMATION_DURATION = 150;
public static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
private SplitLayout mSplitLayout;
private SurfaceControlViewHost mViewHost;
private DragListener mDragListener;
private DividerHandleView mHandle;
private View mBackground;
private int mTouchElevation;
private VelocityTracker mVelocityTracker;
private boolean mMoving;
@@ -70,16 +76,18 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
/** Sets up essential dependencies of the divider bar. */
public void setup(
SplitLayout layout,
SurfaceControlViewHost viewHost,
@Nullable DragListener dragListener) {
SurfaceControlViewHost viewHost) {
mSplitLayout = layout;
mViewHost = viewHost;
mDragListener = dragListener;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mHandle = findViewById(R.id.docked_divider_handle);
mBackground = findViewById(R.id.docked_divider_background);
mTouchElevation = getResources().getDimensionPixelSize(
R.dimen.docked_stack_divider_lift_elevation);
setOnTouchListener(this);
}
@@ -97,7 +105,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
case MotionEvent.ACTION_DOWN:
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(event);
setSlippery(false);
setTouching();
mStartPos = touchPos;
mMoving = false;
break;
@@ -106,9 +114,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
if (!mMoving && Math.abs(touchPos - mStartPos) > mTouchSlop) {
mStartPos = touchPos;
mMoving = true;
if (mDragListener != null) {
mDragListener.onDragStart();
}
}
if (mMoving) {
final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
@@ -122,11 +127,8 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
final float velocity = isLandscape
? mVelocityTracker.getXVelocity()
: mVelocityTracker.getYVelocity();
setSlippery(true);
releaseTouching();
mMoving = false;
if (mDragListener != null) {
mDragListener.onDragEnd();
}
final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
final DividerSnapAlgorithm.SnapTarget snapTarget =
@@ -137,6 +139,45 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
return true;
}
private void setTouching() {
setSlippery(false);
mHandle.setTouching(true, true);
if (isLandscape()) {
mBackground.animate().scaleX(1.4f);
} else {
mBackground.animate().scaleY(1.4f);
}
mBackground.animate()
.setInterpolator(Interpolators.TOUCH_RESPONSE)
.setDuration(TOUCH_ANIMATION_DURATION)
.translationZ(mTouchElevation)
.start();
// Lift handle as well so it doesn't get behind the background, even though it doesn't
// cast shadow.
mHandle.animate()
.setInterpolator(Interpolators.TOUCH_RESPONSE)
.setDuration(TOUCH_ANIMATION_DURATION)
.translationZ(mTouchElevation)
.start();
}
private void releaseTouching() {
setSlippery(true);
mHandle.setTouching(false, true);
mBackground.animate()
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
.translationZ(0)
.scaleX(1f)
.scaleY(1f)
.start();
mHandle.animate()
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
.translationZ(0)
.start();
}
private void setSlippery(boolean slippery) {
if (mViewHost == null) {
return;
@@ -159,13 +200,4 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
private boolean isLandscape() {
return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
}
/** Monitors dragging action of the divider bar. */
// TODO(b/172704238): add listeners to deal with resizing state of the app windows.
public interface DragListener {
/** Called when start dragging. */
void onDragStart();
/** Called when stop dragging. */
void onDragEnd();
}
}

View File

@@ -156,12 +156,14 @@ public class SplitLayout {
void updateDivideBounds(int position) {
updateBounds(position);
mLayoutChangeListener.onBoundsChanging(this);
mSplitWindowManager.setResizingSplits(true);
}
void setDividePosition(int position) {
mDividePosition = position;
updateBounds(mDividePosition);
mLayoutChangeListener.onBoundsChanged(this);
mSplitWindowManager.setResizingSplits(false);
}
/**
@@ -172,9 +174,11 @@ public class SplitLayout {
switch (snapTarget.flag) {
case FLAG_DISMISS_START:
mLayoutChangeListener.onSnappedToDismiss(false /* snappedToEnd */);
mSplitWindowManager.setResizingSplits(false);
break;
case FLAG_DISMISS_END:
mLayoutChangeListener.onSnappedToDismiss(true /* snappedToEnd */);
mSplitWindowManager.setResizingSplits(false);
break;
default:
flingDividePosition(currentPosition, snapTarget.position);

View File

@@ -25,6 +25,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
@@ -32,6 +33,8 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import android.view.IWindow;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
@@ -47,11 +50,13 @@ import com.android.wm.shell.R;
* Holds view hierarchy of a root surface and helps to inflate {@link DividerView} for a split.
*/
public final class SplitWindowManager extends WindowlessWindowManager {
private static final String TAG = SplitWindowManager.class.getSimpleName();
private static final String DIVIDER_WINDOW_TITLE = "SplitDivider";
private final ParentContainerCallbacks mParentContainerCallbacks;
private Context mContext;
private SurfaceControlViewHost mViewHost;
final private ParentContainerCallbacks mParentContainerCallbacks;
private boolean mResizingSplits;
public interface ParentContainerCallbacks {
void attachToParentSurface(SurfaceControl.Builder b);
@@ -104,7 +109,7 @@ public final class SplitWindowManager extends WindowlessWindowManager {
lp.setTitle(DIVIDER_WINDOW_TITLE);
lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
mViewHost.setView(dividerView, lp);
dividerView.setup(splitLayout, mViewHost, null /* dragListener */);
dividerView.setup(splitLayout, mViewHost);
}
/**
@@ -117,6 +122,16 @@ public final class SplitWindowManager extends WindowlessWindowManager {
mViewHost = null;
}
void setResizingSplits(boolean resizing) {
if (resizing == mResizingSplits) return;
try {
ActivityTaskManager.getService().setSplitScreenResizing(resizing);
mResizingSplits = resizing;
} catch (RemoteException e) {
Slog.w(TAG, "Error calling setSplitScreenResizing", e);
}
}
/**
* Gets {@link SurfaceControl} of the surface holding divider view. @return {@code null} if not
* feasible.

View File

@@ -20,6 +20,9 @@ import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
import static android.view.WindowManager.DOCKED_RIGHT;
import static com.android.wm.shell.common.split.DividerView.TOUCH_ANIMATION_DURATION;
import static com.android.wm.shell.common.split.DividerView.TOUCH_RELEASE_ANIMATION_DURATION;
import android.animation.AnimationHandler;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -66,6 +69,7 @@ import com.android.internal.policy.DockedDividerUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.split.DividerHandleView;
import java.util.function.Consumer;
@@ -82,9 +86,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
void onDraggingEnd();
}
static final long TOUCH_ANIMATION_DURATION = 150;
static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
public static final int INVALID_RECENTS_GROW_TARGET = -1;
private static final int LOG_VALUE_RESIZE_50_50 = 0;

View File

@@ -75,15 +75,16 @@ public class SplitLayoutTests extends ShellTestCase {
}
@Test
@UiThreadTest
public void testSnapToTarget() {
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
DividerSnapAlgorithm.SnapTarget.FLAG_NONE);
mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget);
verify(mLayoutChangeListener).onBoundsChanging(any(SplitLayout.class));
public void testSetDividePosition() {
mSplitLayout.setDividePosition(anyInt());
verify(mLayoutChangeListener).onBoundsChanged(any(SplitLayout.class));
}
@Test
@UiThreadTest
public void testSnapToDismissTarget() {
// verify it callbacks properly when the snap target indicates dismissing split.
snapTarget = getSnapTarget(0 /* position */,
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START);
mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget);
verify(mLayoutChangeListener).onSnappedToDismiss(eq(false));

View File

@@ -3843,7 +3843,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (task == null) {
return false;
}
if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()) {
if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()
&& !task.getRootTask().mCreatedByOrganizer) {
return false;
}
// TODO(157912944): formalize drag-resizing so that exceptions aren't hardcoded like this