Allow non-resizable apps in split-screen (7/n)

Add interface for SizeCompatUIController in WM shell to use
when refactoring SizeCompatModeActivityController from system UI.

Bug: 176061101
Bug: 178327644
Test: atest WMShellUnitTests:ShellTaskOrganizerTests
Change-Id: I37f72396e1769b7773722be6575506fc430b2739
This commit is contained in:
Chris Li
2021-01-29 13:32:28 -08:00
parent 0ac1939cd6
commit 98d2d06106
9 changed files with 294 additions and 12 deletions

View File

@@ -336,6 +336,23 @@ public class TaskInfo {
&& isVisible == that.isVisible;
}
/**
* @return {@code true} if parameters that are important for size compat have changed.
* @hide
*/
public boolean equalsForSizeCompat(@Nullable TaskInfo that) {
if (that == null) {
return false;
}
return displayId == that.displayId
&& taskId == that.taskId
&& topActivityInSizeCompat == that.topActivityInSizeCompat
// TopActivityToken and bounds are important if top activity is in size compat
&& (!topActivityInSizeCompat || topActivityToken.equals(that.topActivityToken))
&& (!topActivityInSizeCompat || configuration.windowConfiguration.getBounds()
.equals(that.configuration.windowConfiguration.getBounds()));
}
/**
* Reads the TaskInfo from a parcel.
*/

View File

@@ -106,5 +106,4 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
public String toString() {
return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
}
}

View File

@@ -44,6 +44,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
import java.io.PrintWriter;
@@ -102,18 +103,31 @@ public class ShellTaskOrganizer extends TaskOrganizer {
private final Object mLock = new Object();
private final StartingSurfaceDrawer mStartingSurfaceDrawer;
/**
* In charge of showing size compat UI. Can be {@code null} if device doesn't support size
* compat.
*/
@Nullable
private final SizeCompatUI mSizeCompatUI;
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
this(null, mainExecutor, context);
this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
}
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
SizeCompatUI sizeCompatUI) {
this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
}
@VisibleForTesting
ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
Context context) {
Context context, @Nullable SizeCompatUI sizeCompatUI) {
super(taskOrganizerController, mainExecutor);
// TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
// by a controller, that class should be create while porting
// ActivityRecord#addStartingWindow to WMShell.
mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, mainExecutor);
mSizeCompatUI = sizeCompatUI;
}
@Override
@@ -255,6 +269,7 @@ public class ShellTaskOrganizer extends TaskOrganizer {
if (listener != null) {
listener.onTaskAppeared(info.getTaskInfo(), info.getLeash());
}
notifySizeCompatUI(info.getTaskInfo(), listener);
}
@Override
@@ -270,6 +285,10 @@ public class ShellTaskOrganizer extends TaskOrganizer {
if (!updated && newListener != null) {
newListener.onTaskInfoChanged(taskInfo);
}
if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) {
// Notify the size compat UI if the listener or task info changed.
notifySizeCompatUI(taskInfo, newListener);
}
}
}
@@ -294,6 +313,8 @@ public class ShellTaskOrganizer extends TaskOrganizer {
if (listener != null) {
listener.onTaskVanished(taskInfo);
}
// Pass null for listener to remove the size compat UI on this task if there is any.
notifySizeCompatUI(taskInfo, null /* taskListener */);
}
}
@@ -320,6 +341,34 @@ public class ShellTaskOrganizer extends TaskOrganizer {
return true;
}
/**
* Notifies {@link SizeCompatUI} about the size compat info changed on the give Task to update
* the UI accordingly.
*
* @param taskInfo the new Task info
* @param taskListener listener to handle the Task Surface placement. {@code null} if task is
* vanished.
*/
private void notifySizeCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) {
if (mSizeCompatUI == null) {
return;
}
// The task is vanished, notify to remove size compat UI on this Task if there is any.
if (taskListener == null) {
mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
null /* taskConfig */, null /* sizeCompatActivity*/,
null /* taskListener */);
return;
}
mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
taskInfo.configuration.windowConfiguration.getBounds(),
// null if the top activity not in size compat.
taskInfo.topActivityInSizeCompat ? taskInfo.topActivityToken : null,
taskListener);
}
private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
return getTaskListener(runningTaskInfo, false /*removeLaunchCookieIfNeeded*/);
}

View File

@@ -159,6 +159,14 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
private void dispatchVisibilityChanged(int displayId, boolean isShowing) {
synchronized (mPositionProcessors) {
for (ImePositionProcessor pp : mPositionProcessors) {
pp.onImeVisibilityChanged(displayId, isShowing);
}
}
}
/**
* Adds an {@link ImePositionProcessor} to be called during ime position updates.
*/
@@ -212,7 +220,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
return;
}
mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME);
updateImeVisibility(insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME));
final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
final Rect newFrame = newSource.getFrame();
@@ -371,7 +379,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
seek = true;
}
mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
mImeShowing = show;
updateImeVisibility(show);
mAnimation = ValueAnimator.ofFloat(startY, endY);
mAnimation.setDuration(
show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS);
@@ -455,6 +463,13 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
private void updateImeVisibility(boolean isShowing) {
if (mImeShowing != isShowing) {
mImeShowing = isShowing;
dispatchVisibilityChanged(mDisplayId, isShowing);
}
}
@VisibleForTesting
@BinderThread
public class DisplayWindowInsetsControllerImpl
@@ -563,6 +578,15 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
default void onImeEndPositioning(int displayId, boolean cancel,
SurfaceControl.Transaction t) {
}
/**
* Called when the IME visibility changed.
*
* @param isShowing {@code true} if the IME is shown.
*/
default void onImeVisibilityChanged(int displayId, boolean isShowing) {
}
}
public IInputMethodManager getImms() {

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2021 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.wm.shell.sizecompatui;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.IBinder;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.annotations.ExternalThread;
/**
* Interface to engage size compat mode UI.
*/
@ExternalThread
public interface SizeCompatUI {
/**
* Called when the Task info changed. Creates and updates the restart button if there is an
* activity in size compat, or removes the restart button if there is no size compat activity.
*
* @param displayId display the task and activity are in.
* @param taskId task the activity is in.
* @param taskBounds task bounds to place the restart button in.
* @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
* top activity in this Task is not in size compat.
* @param taskListener listener to handle the Task Surface placement.
*/
void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
@Nullable IBinder sizeCompatActivity,
@Nullable ShellTaskOrganizer.TaskListener taskListener);
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2021 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.wm.shell.sizecompatui;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.ShellExecutor;
/**
* Shows a restart-activity button on Task when the foreground activity is in size compatibility
* mode.
*/
public class SizeCompatUIController implements DisplayController.OnDisplaysChangedListener,
DisplayImeController.ImePositionProcessor {
private static final String TAG = "SizeCompatUI";
@VisibleForTesting
final SizeCompatUI mImpl = new SizeCompatUIImpl();
private final Context mContext;
private final ShellExecutor mMainExecutor;
private final DisplayController mDisplayController;
private final DisplayImeController mImeController;
/** Creates the {@link SizeCompatUIController}. */
public static SizeCompatUI create(Context context,
DisplayController displayController,
DisplayImeController imeController,
ShellExecutor mainExecutor) {
return new SizeCompatUIController(context, displayController, imeController, mainExecutor)
.mImpl;
}
@VisibleForTesting
SizeCompatUIController(Context context,
DisplayController displayController,
DisplayImeController imeController,
ShellExecutor mainExecutor) {
mContext = context;
mMainExecutor = mainExecutor;
mDisplayController = displayController;
mImeController = imeController;
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
}
private void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
@Nullable IBinder sizeCompatActivity,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
// TODO need to deduplicate task info changed
}
// TODO move from SizeCompatModeActivityController from system UI.
@Override
public void onDisplayRemoved(int displayId) {
}
@Override
public void onImeVisibilityChanged(int displayId, boolean isShowing) {
}
private class SizeCompatUIImpl implements SizeCompatUI {
@Override
public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
@Nullable IBinder sizeCompatActivity,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
mMainExecutor.execute(() ->
SizeCompatUIController.this.onSizeCompatInfoChanged(displayId, taskId,
taskBounds, sizeCompatActivity, taskListener));
}
}
}

View File

@@ -16,11 +16,14 @@
package com.android.wm.shell;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
@@ -48,6 +51,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.sizecompatui.SizeCompatUI;
import org.junit.Before;
import org.junit.Test;
@@ -59,6 +63,9 @@ import java.util.ArrayList;
/**
* Tests for the shell task organizer.
*
* Build/Install/Run:
* atest WMShellUnitTests:ShellTaskOrganizerTests
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -68,6 +75,8 @@ public class ShellTaskOrganizerTests {
private ITaskOrganizerController mTaskOrganizerController;
@Mock
private Context mContext;
@Mock
private SizeCompatUI mSizeCompatUI;
ShellTaskOrganizer mOrganizer;
private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
@@ -102,7 +111,8 @@ public class ShellTaskOrganizerTests {
doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext));
mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
mSizeCompatUI));
}
@Test
@@ -257,6 +267,39 @@ public class ShellTaskOrganizerTests {
assertTrue(mwListener.appeared.contains(task2));
}
@Test
public void testOnSizeCompatActivityChanged() {
final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
taskInfo1.topActivityToken = mock(IBinder.class);
taskInfo1.topActivityInSizeCompat = false;
final TrackingTaskListener taskListener = new TrackingTaskListener();
mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
mOrganizer.onTaskAppeared(taskInfo1, null);
// sizeCompatActivity is null if top activity is not in size compat.
verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
taskInfo1.configuration.windowConfiguration.getBounds(),
null /* sizeCompatActivity*/ , taskListener);
// sizeCompatActivity is non-null if top activity is in size compat.
final RunningTaskInfo taskInfo2 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo2.displayId = taskInfo1.displayId;
taskInfo2.topActivityToken = taskInfo1.topActivityToken;
taskInfo2.topActivityInSizeCompat = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
taskInfo1.configuration.windowConfiguration.getBounds(),
taskInfo1.topActivityToken,
taskListener);
mOrganizer.onTaskVanished(taskInfo1);
verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
null /* taskConfig */, null /* sizeCompatActivity*/,
null /* taskListener */);
}
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;

View File

@@ -71,6 +71,8 @@ import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -293,8 +295,8 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
Context context) {
return new ShellTaskOrganizer(mainExecutor, context);
Context context, SizeCompatUI sizeCompatUI) {
return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
}
@WMSingleton
@@ -380,8 +382,7 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static FullscreenTaskListener provideFullscreenTaskListener(
SyncTransactionQueue syncQueue) {
static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
return new FullscreenTaskListener(syncQueue);
}
@@ -392,4 +393,12 @@ public abstract class WMShellBaseModule {
@ShellAnimationThread ShellExecutor animExecutor) {
return new Transitions(organizer, pool, mainExecutor, animExecutor);
}
@WMSingleton
@Provides
static SizeCompatUI provideSizeCompatUI(Context context, DisplayController displayController,
DisplayImeController imeController, @ShellMainThread ShellExecutor mainExecutor) {
return SizeCompatUIController.create(context, displayController, imeController,
mainExecutor);
}
}

View File

@@ -18,7 +18,6 @@ package com.android.systemui.wmshell;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.test.suitebuilder.annotation.SmallTest;
@@ -41,7 +40,6 @@ import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedGestureHandler;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import org.junit.Before;
import org.junit.Test;
@@ -51,6 +49,12 @@ import org.mockito.MockitoAnnotations;
import java.util.Optional;
/**
* Tests for {@link WMShell}.
*
* Build/Install/Run:
* atest SystemUITests:WMShellTest
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WMShellTest extends SysuiTestCase {