diff options
37 files changed, 1625 insertions, 873 deletions
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 180507cd7e9c..3c475c1a8083 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -315,11 +315,6 @@ interface IActivityTaskManager { void positionTaskInStack(int taskId, int stackId, int position); void reportSizeConfigurations(in IBinder token, in int[] horizontalSizeConfiguration, in int[] verticalSizeConfigurations, in int[] smallestWidthConfigurations); - /** - * Dismisses split-screen multi-window mode. - * {@param toTop} If true the current primary split-screen stack will be placed or left on top. - */ - void dismissSplitScreenMode(boolean toTop); /** * Dismisses PiP diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/core/java/com/android/internal/policy/DockedDividerUtils.java index c68e50692c2a..b61b9dea2554 100644 --- a/core/java/com/android/internal/policy/DockedDividerUtils.java +++ b/core/java/com/android/internal/policy/DockedDividerUtils.java @@ -16,14 +16,15 @@ package com.android.internal.policy; -import android.graphics.Rect; - import static android.view.WindowManager.DOCKED_BOTTOM; import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.DOCKED_TOP; +import android.content.res.Resources; +import android.graphics.Rect; + /** * Utility functions for docked stack divider used by both window manager and System UI. * @@ -105,23 +106,6 @@ public class DockedDividerUtils { return start + (end - start) / 2 - dividerSize / 2; } - public static int getDockSideFromCreatedMode(boolean dockOnTopOrLeft, - boolean isHorizontalDivision) { - if (dockOnTopOrLeft) { - if (isHorizontalDivision) { - return DOCKED_TOP; - } else { - return DOCKED_LEFT; - } - } else { - if (isHorizontalDivision) { - return DOCKED_BOTTOM; - } else { - return DOCKED_RIGHT; - } - } - } - public static int invertDockSide(int dockSide) { switch (dockSide) { case DOCKED_LEFT: @@ -136,4 +120,21 @@ public class DockedDividerUtils { return DOCKED_INVALID; } } + + /** Returns the inset distance from the divider window edge to the dividerview. */ + public static int getDividerInsets(Resources res) { + return res.getDimensionPixelSize(com.android.internal.R.dimen.docked_stack_divider_insets); + } + + /** Returns the size of the divider */ + public static int getDividerSize(Resources res, int dividerInsets) { + final int windowWidth = res.getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_divider_thickness); + return windowWidth - 2 * dividerInsets; + } + + /** Returns the docked-stack side */ + public static int getDockSide(int displayWidth, int displayHeight) { + return displayWidth > displayHeight ? DOCKED_LEFT : DOCKED_TOP; + } } diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index b0b9ce6f9968..08db4544d5e7 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -275,6 +275,7 @@ message TaskProto { optional float adjust_divider_amount = 25; optional bool animating_bounds = 26; optional float minimize_amount = 27; + optional bool created_by_organizer = 28; } /* represents ActivityRecordProto */ diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 2d288ff40b2c..b1d39f59f789 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -123,4 +123,9 @@ interface ISystemUiProxy { */ void handleImageAsScreenshot(in Bitmap screenImage, in Rect locationInScreen, in Insets visibleInsets, int taskId) = 21; + + /** + * Sets the split-screen divider minimized state + */ + void setSplitScreenMinimized(boolean minimized) = 22; } diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 27fe37ef4e69..dc5cb1f9fb6b 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -61,6 +61,7 @@ import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.DevicePolicyManagerWrapper; import com.android.systemui.shared.system.PackageManagerWrapper; +import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.NotificationListener; @@ -329,6 +330,7 @@ public class Dependency { @Inject Lazy<DisplayImeController> mDisplayImeController; @Inject Lazy<RecordingController> mRecordingController; @Inject Lazy<ProtoTracer> mProtoTracer; + @Inject Lazy<Divider> mDivider; @Inject public Dependency() { @@ -530,6 +532,7 @@ public class Dependency { mProviders.put(AutoHideController.class, mAutoHideController::get); mProviders.put(RecordingController.class, mRecordingController::get); + mProviders.put(Divider.class, mDivider::get); sDependency = this; } diff --git a/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java b/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java deleted file mode 100644 index 5c0df179dd27..000000000000 --- a/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2017 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; - -import android.os.RemoteException; -import android.util.Log; -import android.view.IDockedStackListener; -import android.view.WindowManagerGlobal; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.function.Consumer; - -/** - * Utility wrapper to listen for whether or not a docked stack exists, to be - * used for things like the different overview icon in that mode. - */ -public class DockedStackExistsListener { - - private static final String TAG = "DockedStackExistsListener"; - - private static ArrayList<WeakReference<Consumer<Boolean>>> sCallbacks = new ArrayList<>(); - private static boolean mLastExists; - - static { - try { - WindowManagerGlobal.getWindowManagerService().registerDockedStackListener( - new IDockedStackListener.Stub() { - @Override - public void onDividerVisibilityChanged(boolean b) throws RemoteException { - - } - - @Override - public void onDockedStackExistsChanged(boolean exists) - throws RemoteException { - DockedStackExistsListener.onDockedStackExistsChanged(exists); - } - - @Override - public void onDockedStackMinimizedChanged(boolean b, long l, boolean b1) - throws RemoteException { - - } - - @Override - public void onAdjustedForImeChanged(boolean b, long l) - throws RemoteException { - - } - - @Override - public void onDockSideChanged(int i) throws RemoteException { - - } - }); - } catch (RemoteException e) { - Log.e(TAG, "Failed registering docked stack exists listener", e); - } - } - - - private static void onDockedStackExistsChanged(boolean exists) { - mLastExists = exists; - synchronized (sCallbacks) { - sCallbacks.removeIf(wf -> { - Consumer<Boolean> l = wf.get(); - if (l != null) l.accept(exists); - return l == null; - }); - } - } - - public static void register(Consumer<Boolean> callback) { - callback.accept(mLastExists); - synchronized (sCallbacks) { - sCallbacks.add(new WeakReference<>(callback)); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 9f64b397e9d9..fd484ef61c2d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -380,6 +380,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis taskId, mHandler, null); } + @Override + public void setSplitScreenMinimized(boolean minimized) { + Divider divider = mDividerOptional.get(); + if (divider != null) { + divider.setMinimized(minimized); + } + } + private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java index 5ae095421a80..4f20492c60a3 100644 --- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java @@ -22,10 +22,8 @@ import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LE import android.content.Context; import android.content.res.Configuration; import android.os.RemoteException; -import android.util.Log; import android.view.IWindowManager; import android.view.KeyEvent; -import android.view.WindowManager; import android.view.WindowManagerGlobal; import com.android.internal.policy.DividerSnapAlgorithm; @@ -94,29 +92,24 @@ public class ShortcutKeyDispatcher extends SystemUI } private void handleDockKey(long shortcutCode) { - try { - int dockSide = mWindowManagerService.getDockedStackSide(); - if (dockSide == WindowManager.DOCKED_INVALID) { - // Split the screen - mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT) - ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT - : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1); - } else { - // If there is already a docked window, we respond by resizing the docking pane. - DividerView dividerView = mDivider.getView(); - DividerSnapAlgorithm snapAlgorithm = dividerView.getSnapAlgorithm(); - int dividerPosition = dividerView.getCurrentPosition(); - DividerSnapAlgorithm.SnapTarget currentTarget = - snapAlgorithm.calculateNonDismissingSnapTarget(dividerPosition); - DividerSnapAlgorithm.SnapTarget target = (shortcutCode == SC_DOCK_LEFT) - ? snapAlgorithm.getPreviousTarget(currentTarget) - : snapAlgorithm.getNextTarget(currentTarget); - dividerView.startDragging(true /* animate */, false /* touching */); - dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */, - true /* logMetrics */); - } - } catch (RemoteException e) { - Log.e(TAG, "handleDockKey() failed."); + if (mDivider == null || !mDivider.inSplitMode()) { + // Split the screen + mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT) + ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT + : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1); + } else { + // If there is already a docked window, we respond by resizing the docking pane. + DividerView dividerView = mDivider.getView(); + DividerSnapAlgorithm snapAlgorithm = dividerView.getSnapAlgorithm(); + int dividerPosition = dividerView.getCurrentPosition(); + DividerSnapAlgorithm.SnapTarget currentTarget = + snapAlgorithm.calculateNonDismissingSnapTarget(dividerPosition); + DividerSnapAlgorithm.SnapTarget target = (shortcutCode == SC_DOCK_LEFT) + ? snapAlgorithm.getPreviousTarget(currentTarget) + : snapAlgorithm.getNextTarget(currentTarget); + dividerView.startDragging(true /* animate */, false /* touching */); + dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */, + true /* logMetrics */); } } } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java index 90cc0e57f50c..ba9eb4a6ede1 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java @@ -17,69 +17,281 @@ package com.android.systemui.stackdivider; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.Display.DEFAULT_DISPLAY; +import android.app.ActivityTaskManager; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Handler; import android.os.RemoteException; +import android.provider.Settings; import android.util.Log; -import android.view.IDockedStackListener; +import android.util.Slog; +import android.view.IWindowContainer; import android.view.LayoutInflater; +import android.view.SurfaceControl; +import android.view.SurfaceSession; import android.view.View; -import android.view.WindowManagerGlobal; +import android.view.WindowContainerTransaction; +import com.android.internal.policy.DividerSnapAlgorithm; import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.recents.Recents; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.wm.DisplayChangeController; +import com.android.systemui.wm.DisplayController; +import com.android.systemui.wm.DisplayImeController; +import com.android.systemui.wm.DisplayLayout; +import com.android.systemui.wm.SystemWindows; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Optional; +import java.util.function.Consumer; + +import javax.inject.Singleton; import dagger.Lazy; /** * Controls the docked stack divider. */ -public class Divider extends SystemUI implements DividerView.DividerCallbacks { +@Singleton +public class Divider extends SystemUI implements DividerView.DividerCallbacks, + DisplayController.OnDisplaysChangedListener { private static final String TAG = "Divider"; + + static final boolean DEBUG = true; + + static final int DEFAULT_APP_TRANSITION_DURATION = 336; + private final Optional<Lazy<Recents>> mRecentsOptionalLazy; private DividerWindowManager mWindowManager; private DividerView mView; private final DividerState mDividerState = new DividerState(); - private DockDividerVisibilityListener mDockDividerVisibilityListener; private boolean mVisible = false; private boolean mMinimized = false; private boolean mAdjustedForIme = false; private boolean mHomeStackResizable = false; private ForcedResizableInfoActivityController mForcedResizableController; + private SystemWindows mSystemWindows; + final SurfaceSession mSurfaceSession = new SurfaceSession(); + private DisplayController mDisplayController; + private DisplayImeController mImeController; + + // Keeps track of real-time split geometry including snap positions and ime adjustments + private SplitDisplayLayout mSplitLayout; + + // Transient: this contains the layout calculated for a new rotation requested by WM. This is + // kept around so that we can wait for a matching configuration change and then use the exact + // layout that we sent back to WM. + private SplitDisplayLayout mRotateSplitLayout; + + private Handler mHandler; + private KeyguardStateController mKeyguardStateController; + + private final ArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners = + new ArrayList<>(); + + private SplitScreenTaskOrganizer mSplits = new SplitScreenTaskOrganizer(this); + + private DisplayChangeController.OnDisplayChangingListener mRotationController = + (display, fromRotation, toRotation, t) -> { + DisplayLayout displayLayout = + new DisplayLayout(mDisplayController.getDisplayLayout(display)); + SplitDisplayLayout sdl = new SplitDisplayLayout(mContext, displayLayout, mSplits); + sdl.rotateTo(toRotation); + mRotateSplitLayout = sdl; + int position = mMinimized ? mView.mSnapTargetBeforeMinimized.position + : mView.getCurrentPosition(); + DividerSnapAlgorithm snap = sdl.getSnapAlgorithm(); + final DividerSnapAlgorithm.SnapTarget target = + snap.calculateNonDismissingSnapTarget(position); + sdl.resizeSplits(target.position, t); + + if (inSplitMode()) { + WindowManagerProxy.applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t); + } + }; + + private IWindowContainer mLastImeTarget = null; + private boolean mShouldAdjustForIme = false; + + private DisplayImeController.ImePositionProcessor mImePositionProcessor = + new DisplayImeController.ImePositionProcessor() { + private int mStartTop = 0; + private int mFinalTop = 0; + @Override + public void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, + boolean showing, SurfaceControl.Transaction t) { + mStartTop = imeTop; + mFinalTop = finalImeTop; + if (showing) { + try { + mLastImeTarget = ActivityTaskManager.getTaskOrganizerController() + .getImeTarget(displayId); + mShouldAdjustForIme = !mSplitLayout.mDisplayLayout.isLandscape() + && (mLastImeTarget.asBinder() + == mSplits.mSecondary.token.asBinder()); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to get IME target", e); + } + } + if (!mShouldAdjustForIme) { + setAdjustedForIme(false); + return; + } + mView.setAdjustedForIme(showing, showing + ? DisplayImeController.ANIMATION_DURATION_SHOW_MS + : DisplayImeController.ANIMATION_DURATION_HIDE_MS); + // Reposition the server's secondary split position so that it evaluates + // insets properly. + WindowContainerTransaction wct = new WindowContainerTransaction(); + if (showing) { + mSplitLayout.updateAdjustedBounds(finalImeTop, imeTop, finalImeTop); + wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mAdjustedSecondary); + } else { + wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mSecondary); + } + try { + ActivityTaskManager.getTaskOrganizerController() + .applyContainerTransaction(wct, null /* organizer */); + } catch (RemoteException e) { + } + setAdjustedForIme(showing); + } + + @Override + public void onImePositionChanged(int displayId, int imeTop, + SurfaceControl.Transaction t) { + if (!mShouldAdjustForIme) { + return; + } + mSplitLayout.updateAdjustedBounds(imeTop, mStartTop, mFinalTop); + mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary, + mSplitLayout.mAdjustedSecondary); + final boolean showing = mFinalTop < mStartTop; + final float progress = ((float) (imeTop - mStartTop)) / (mFinalTop - mStartTop); + final float fraction = showing ? progress : 1.f - progress; + mView.setResizeDimLayer(t, true /* primary */, fraction * 0.3f); + } + + @Override + public void onImeEndPositioning(int displayId, int imeTop, + boolean showing, SurfaceControl.Transaction t) { + if (!mShouldAdjustForIme) { + return; + } + mSplitLayout.updateAdjustedBounds(imeTop, mStartTop, mFinalTop); + mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary, + mSplitLayout.mAdjustedSecondary); + mView.setResizeDimLayer(t, true /* primary */, showing ? 0.3f : 0.f); + } + }; - public Divider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy) { + public Divider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy, + DisplayController displayController, SystemWindows systemWindows, + DisplayImeController imeController, Handler handler, + KeyguardStateController keyguardStateController) { super(context); + mDisplayController = displayController; + mSystemWindows = systemWindows; + mImeController = imeController; + mHandler = handler; + mKeyguardStateController = keyguardStateController; mRecentsOptionalLazy = recentsOptionalLazy; + mForcedResizableController = new ForcedResizableInfoActivityController(context, this); } @Override public void start() { - mWindowManager = new DividerWindowManager(mContext); - update(mContext.getResources().getConfiguration()); - mDockDividerVisibilityListener = new DockDividerVisibilityListener(); + mWindowManager = new DividerWindowManager(mSystemWindows); + mDisplayController.addDisplayWindowListener(this); + // Hide the divider when keyguard is showing. Even though keyguard/statusbar is above + // everything, it is actually transparent except for notifications, so we still need to + // hide any surfaces that are below it. + // TODO(b/148906453): Figure out keyguard dismiss animation for divider view. + mKeyguardStateController.addCallback(new KeyguardStateController.Callback() { + @Override + public void onUnlockedChanged() { + + } + + @Override + public void onKeyguardShowingChanged() { + if (!inSplitMode() || mView == null || mView.getViewRootImpl() == null + || mView.getViewRootImpl().getSurfaceControl() == null) { + return; + } + mView.setHidden(mKeyguardStateController.isShowing()); + } + + @Override + public void onKeyguardFadingAwayChanged() { + + } + }); + // Don't initialize the divider or anything until we get the default display. + } + + @Override + public void onDisplayAdded(int displayId) { + if (displayId != DEFAULT_DISPLAY) { + return; + } + mSplitLayout = new SplitDisplayLayout(mDisplayController.getDisplayContext(displayId), + mDisplayController.getDisplayLayout(displayId), mSplits); + mImeController.addPositionProcessor(mImePositionProcessor); + mDisplayController.addDisplayChangingController(mRotationController); try { - WindowManagerGlobal.getWindowManagerService().registerDockedStackListener( - mDockDividerVisibilityListener); + mSplits.init(ActivityTaskManager.getTaskOrganizerController(), mSurfaceSession); + // Set starting tile bounds based on middle target + final WindowContainerTransaction tct = new WindowContainerTransaction(); + int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; + mSplitLayout.resizeSplits(midPos, tct); + ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(tct, + null /* organizer */); } catch (Exception e) { - Log.e(TAG, "Failed to register docked stack listener", e); + Slog.e(TAG, "Failed to register docked stack listener", e); } - mForcedResizableController = new ForcedResizableInfoActivityController(mContext); + update(mDisplayController.getDisplayContext(displayId).getResources().getConfiguration()); } @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + if (displayId != DEFAULT_DISPLAY) { + return; + } + mSplitLayout = new SplitDisplayLayout(mDisplayController.getDisplayContext(displayId), + mDisplayController.getDisplayLayout(displayId), mSplits); + if (mRotateSplitLayout == null) { + int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; + final WindowContainerTransaction tct = new WindowContainerTransaction(); + mSplitLayout.resizeSplits(midPos, tct); + try { + ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(tct, + null /* organizer */); + } catch (RemoteException e) { + } + } else if (mRotateSplitLayout != null + && mSplitLayout.mDisplayLayout.rotation() + == mRotateSplitLayout.mDisplayLayout.rotation()) { + mSplitLayout.mPrimary = new Rect(mRotateSplitLayout.mPrimary); + mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary); + mRotateSplitLayout = null; + } update(newConfig); } + Handler getHandler() { + return mHandler; + } + public DividerView getView() { return mView; } @@ -92,18 +304,25 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks { return mHomeStackResizable; } + /** {@code true} if this is visible */ + public boolean inSplitMode() { + return mView != null && mView.getVisibility() == View.VISIBLE; + } + private void addDivider(Configuration configuration) { + Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId()); mView = (DividerView) - LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null); - mView.injectDependencies(mWindowManager, mDividerState, this); + LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null); + DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId()); + mView.injectDependencies(mWindowManager, mDividerState, this, mSplits, mSplitLayout); mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE); mView.setMinimizedDockStack(mMinimized, mHomeStackResizable); - final int size = mContext.getResources().getDimensionPixelSize( + final int size = dctx.getResources().getDimensionPixelSize( com.android.internal.R.dimen.docked_stack_divider_thickness); final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE; - final int width = landscape ? size : MATCH_PARENT; - final int height = landscape ? MATCH_PARENT : size; - mWindowManager.add(mView, width, height); + final int width = landscape ? size : displayLayout.width(); + final int height = landscape ? displayLayout.height() : size; + mWindowManager.add(mView, width, height, mContext.getDisplayId()); } private void removeDivider() { @@ -116,63 +335,84 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks { private void update(Configuration configuration) { removeDivider(); addDivider(configuration); - if (mMinimized) { + if (mMinimized && mView != null) { mView.setMinimizedDockStack(true, mHomeStackResizable); updateTouchable(); } } - private void updateVisibility(final boolean visible) { - mView.post(new Runnable() { - @Override - public void run() { - if (mVisible != visible) { - mVisible = visible; - mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - - // Update state because animations won't finish. - mView.setMinimizedDockStack(mMinimized, mHomeStackResizable); - } + void updateVisibility(final boolean visible) { + if (mVisible != visible) { + mVisible = visible; + mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); + + if (visible) { + mView.enterSplitMode(mHomeStackResizable); + // Update state because animations won't finish. + mView.setMinimizedDockStack(mMinimized, mHomeStackResizable); + } else { + mView.exitSplitMode(); + // un-minimize so that next entry triggers minimize anim. + mView.setMinimizedDockStack(false /* minimized */, mHomeStackResizable); } - }); + // Notify existence listeners + synchronized (mDockedStackExistsListeners) { + mDockedStackExistsListeners.removeIf(wf -> { + Consumer<Boolean> l = wf.get(); + if (l != null) l.accept(visible); + return l == null; + }); + } + } + } + + private void setHomeStackResizable(boolean resizable) { + if (mHomeStackResizable == resizable) { + return; + } + mHomeStackResizable = resizable; + if (!inSplitMode()) { + return; + } + WindowManagerProxy.applyHomeTasksMinimized(mSplitLayout, mSplits.mSecondary.token); } private void updateMinimizedDockedStack(final boolean minimized, final long animDuration, final boolean isHomeStackResizable) { - mView.post(new Runnable() { - @Override - public void run() { - mHomeStackResizable = isHomeStackResizable; - if (mMinimized != minimized) { - mMinimized = minimized; - updateTouchable(); - if (animDuration > 0) { - mView.setMinimizedDockStack(minimized, animDuration, isHomeStackResizable); - } else { - mView.setMinimizedDockStack(minimized, isHomeStackResizable); - } - } - } - }); + setHomeStackResizable(isHomeStackResizable); + if (animDuration > 0) { + mView.setMinimizedDockStack(minimized, animDuration, isHomeStackResizable); + } else { + mView.setMinimizedDockStack(minimized, isHomeStackResizable); + } + updateTouchable(); } - private void notifyDockedStackExistsChanged(final boolean exists) { - mView.post(new Runnable() { - @Override - public void run() { - mForcedResizableController.notifyDockedStackExistsChanged(exists); + /** Switch to minimized state if appropriate */ + public void setMinimized(final boolean minimized) { + mHandler.post(() -> { + if (!inSplitMode()) { + return; + } + if (mMinimized == minimized) { + return; } + mMinimized = minimized; + mView.setMinimizedDockStack(minimized, getAnimDuration(), mHomeStackResizable); + updateTouchable(); }); } - private void updateTouchable() { - mWindowManager.setTouchable((mHomeStackResizable || !mMinimized) && !mAdjustedForIme); + void setAdjustedForIme(boolean adjustedForIme) { + if (mAdjustedForIme == adjustedForIme) { + return; + } + mAdjustedForIme = adjustedForIme; + updateTouchable(); } - public void onRecentsActivityStarting() { - if (mView != null) { - mView.onRecentsActivityStarting(); - } + private void updateTouchable() { + mWindowManager.setTouchable((mHomeStackResizable || !mMinimized) && !mAdjustedForIme); } /** @@ -206,6 +446,9 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks { } public void onAppTransitionFinished() { + if (mView == null) { + return; + } mForcedResizableController.onAppTransitionFinished(); } @@ -231,46 +474,66 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks { pw.print(" mAdjustedForIme="); pw.println(mAdjustedForIme); } - class DockDividerVisibilityListener extends IDockedStackListener.Stub { + long getAnimDuration() { + float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(), + Settings.Global.TRANSITION_ANIMATION_SCALE, + mContext.getResources().getFloat( + com.android.internal.R.dimen + .config_appTransitionAnimationDurationScaleDefault)); + final long transitionDuration = DEFAULT_APP_TRANSITION_DURATION; + return (long) (transitionDuration * transitionScale); + } - @Override - public void onDividerVisibilityChanged(boolean visible) throws RemoteException { - updateVisibility(visible); + /** Register a listener that gets called whenever the existence of the divider changes */ + public void registerInSplitScreenListener(Consumer<Boolean> listener) { + listener.accept(inSplitMode()); + synchronized (mDockedStackExistsListeners) { + mDockedStackExistsListeners.add(new WeakReference<>(listener)); } + } - @Override - public void onDockedStackExistsChanged(boolean exists) throws RemoteException { - notifyDockedStackExistsChanged(exists); - } + void startEnterSplit() { + // Set resizable directly here because applyEnterSplit already resizes home stack. + mHomeStackResizable = WindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout); + } - @Override - public void onDockedStackMinimizedChanged(boolean minimized, long animDuration, - boolean isHomeStackResizable) throws RemoteException { - mHomeStackResizable = isHomeStackResizable; - updateMinimizedDockedStack(minimized, animDuration, isHomeStackResizable); + void ensureMinimizedSplit() { + final boolean wasMinimized = mMinimized; + mMinimized = true; + setHomeStackResizable(mSplits.mSecondary.isResizable()); + if (!inSplitMode()) { + // Wasn't in split-mode yet, so enter now. + if (DEBUG) { + Log.d(TAG, " entering split mode with minimized=true"); + } + updateVisibility(true /* visible */); + } else if (!wasMinimized) { + if (DEBUG) { + Log.d(TAG, " in split mode, but minimizing "); + } + // Was already in split-mode, update just minimized state. + updateMinimizedDockedStack(mMinimized, getAnimDuration(), + mHomeStackResizable); } + } - @Override - public void onAdjustedForImeChanged(boolean adjustedForIme, long animDuration) - throws RemoteException { - mView.post(() -> { - if (mAdjustedForIme != adjustedForIme) { - mAdjustedForIme = adjustedForIme; - updateTouchable(); - if (!mMinimized) { - if (animDuration > 0) { - mView.setAdjustedForIme(adjustedForIme, animDuration); - } else { - mView.setAdjustedForIme(adjustedForIme); - } - } - } - }); + void ensureNormalSplit() { + if (!inSplitMode()) { + // Wasn't in split-mode, so enter now. + if (DEBUG) { + Log.d(TAG, " enter split mode unminimized "); + } + mMinimized = false; + updateVisibility(true /* visible */); } - - @Override - public void onDockSideChanged(final int newDockSide) throws RemoteException { - mView.post(() -> mView.notifyDockSideChanged(newDockSide)); + if (mMinimized) { + // Was in minimized state, so leave that. + if (DEBUG) { + Log.d(TAG, " in split mode already, but unminimizing "); + } + mMinimized = false; + updateMinimizedDockedStack(mMinimized, getAnimDuration(), + mHomeStackResizable); } } } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java index 49f4d5e91659..f3b25535e128 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java @@ -17,8 +17,14 @@ package com.android.systemui.stackdivider; import android.content.Context; +import android.os.Handler; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.recents.Recents; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.wm.DisplayController; +import com.android.systemui.wm.DisplayImeController; +import com.android.systemui.wm.SystemWindows; import java.util.Optional; @@ -35,7 +41,11 @@ import dagger.Provides; public class DividerModule { @Singleton @Provides - static Divider provideDivider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy) { - return new Divider(context, recentsOptionalLazy); + static Divider provideDivider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy, + DisplayController displayController, SystemWindows systemWindows, + DisplayImeController imeController, @Main Handler handler, + KeyguardStateController keyguardStateController) { + return new Divider(context, recentsOptionalLazy, displayController, systemWindows, + imeController, handler, keyguardStateController); } } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index 9fe6e844e0d2..fdd04b9d5858 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -16,12 +16,8 @@ package com.android.systemui.stackdivider; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; -import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; @@ -40,10 +36,11 @@ import android.os.Message; import android.util.AttributeSet; import android.view.Choreographer; import android.view.Display; -import android.view.DisplayInfo; import android.view.InsetsState; import android.view.MotionEvent; import android.view.PointerIcon; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; import android.view.VelocityTracker; import android.view.View; import android.view.View.OnTouchListener; @@ -75,6 +72,7 @@ import com.android.systemui.statusbar.FlingAnimationUtils; */ public class DividerView extends FrameLayout implements OnTouchListener, OnComputeInternalInsetsListener { + private static final String TAG = "DividerView"; public interface DividerCallbacks { void onDraggingStart(); @@ -123,14 +121,11 @@ public class DividerView extends FrameLayout implements OnTouchListener, private int mTouchSlop; private boolean mBackgroundLifted; private boolean mIsInMinimizeInteraction; - private SnapTarget mSnapTargetBeforeMinimized; + SnapTarget mSnapTargetBeforeMinimized; private int mDividerInsets; private final Display mDefaultDisplay; - private int mDisplayWidth; - private int mDisplayHeight; - private int mDisplayRotation; - private int mDividerWindowWidth; + private int mDividerSize; private int mTouchElevation; private int mLongPressEntraceAnimDuration; @@ -147,8 +142,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, private DividerWindowManager mWindowManager; private VelocityTracker mVelocityTracker; private FlingAnimationUtils mFlingAnimationUtils; - private DividerSnapAlgorithm mSnapAlgorithm; - private DividerSnapAlgorithm mMinimizedSnapAlgorithm; + private SplitDisplayLayout mSplitLayout; private DividerCallbacks mCallback; private final Rect mStableInsets = new Rect(); @@ -163,6 +157,10 @@ public class DividerView extends FrameLayout implements OnTouchListener, private DividerState mState; private final SurfaceFlingerVsyncChoreographer mSfChoreographer; + private SplitScreenTaskOrganizer mTiles; + boolean mFirstLayout = true; + int mDividerPositionX; + int mDividerPositionY; // The view is removed or in the process of been removed from the system. private boolean mRemoved; @@ -172,7 +170,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, public void handleMessage(Message msg) { switch (msg.what) { case MSG_RESIZE_STACK: - resizeStack(msg.arg1, msg.arg2, (SnapTarget) msg.obj); + resizeStackSurfaces(msg.arg1, msg.arg2, (SnapTarget) msg.obj); break; default: super.handleMessage(msg); @@ -228,16 +226,17 @@ public class DividerView extends FrameLayout implements OnTouchListener, public boolean performAccessibilityAction(View host, int action, Bundle args) { int currentPosition = getCurrentPosition(); SnapTarget nextTarget = null; + DividerSnapAlgorithm snapAlgorithm = mSplitLayout.getSnapAlgorithm(); if (action == R.id.action_move_tl_full) { - nextTarget = mSnapAlgorithm.getDismissEndTarget(); + nextTarget = snapAlgorithm.getDismissEndTarget(); } else if (action == R.id.action_move_tl_70) { - nextTarget = mSnapAlgorithm.getLastSplitTarget(); + nextTarget = snapAlgorithm.getLastSplitTarget(); } else if (action == R.id.action_move_tl_50) { - nextTarget = mSnapAlgorithm.getMiddleTarget(); + nextTarget = snapAlgorithm.getMiddleTarget(); } else if (action == R.id.action_move_tl_30) { - nextTarget = mSnapAlgorithm.getFirstSplitTarget(); + nextTarget = snapAlgorithm.getFirstSplitTarget(); } else if (action == R.id.action_move_rb_full) { - nextTarget = mSnapAlgorithm.getDismissStartTarget(); + nextTarget = snapAlgorithm.getDismissStartTarget(); } if (nextTarget != null) { startDragging(true /* animate */, false /* touching */); @@ -284,11 +283,11 @@ public class DividerView extends FrameLayout implements OnTouchListener, mBackground = findViewById(R.id.docked_divider_background); mMinimizedShadow = findViewById(R.id.minimized_dock_shadow); mHandle.setOnTouchListener(this); - mDividerWindowWidth = getResources().getDimensionPixelSize( + final int dividerWindowWidth = getResources().getDimensionPixelSize( com.android.internal.R.dimen.docked_stack_divider_thickness); mDividerInsets = getResources().getDimensionPixelSize( com.android.internal.R.dimen.docked_stack_divider_insets); - mDividerSize = mDividerWindowWidth - 2 * mDividerInsets; + mDividerSize = dividerWindowWidth - 2 * mDividerInsets; mTouchElevation = getResources().getDimensionPixelSize( R.dimen.docked_stack_divider_lift_elevation); mLongPressEntraceAnimDuration = getResources().getInteger( @@ -296,7 +295,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow); mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); mFlingAnimationUtils = new FlingAnimationUtils(getResources().getDisplayMetrics(), 0.3f); - updateDisplayInfo(); boolean landscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(), @@ -314,6 +312,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, && !mIsInMinimizeInteraction) { saveSnapTargetBeforeMinimized(mSnapTargetBeforeMinimized); } + mFirstLayout = true; } void onDividerRemoved() { @@ -341,17 +340,17 @@ public class DividerView extends FrameLayout implements OnTouchListener, || mStableInsets.bottom != insets.getStableInsetBottom()) { mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(), insets.getStableInsetRight(), insets.getStableInsetBottom()); - if (mSnapAlgorithm != null || mMinimizedSnapAlgorithm != null) { - mSnapAlgorithm = null; - mMinimizedSnapAlgorithm = null; - initializeSnapAlgorithm(); - } } return super.onApplyWindowInsets(insets); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (mFirstLayout) { + // Wait for first layout so that the ViewRootImpl surface has been created. + initializeSurfaceState(); + mFirstLayout = false; + } super.onLayout(changed, left, top, right, bottom); int minimizeLeft = 0; int minimizeTop = 0; @@ -372,19 +371,16 @@ public class DividerView extends FrameLayout implements OnTouchListener, } public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState, - DividerCallbacks callback) { + DividerCallbacks callback, SplitScreenTaskOrganizer tiles, SplitDisplayLayout sdl) { mWindowManager = windowManager; mState = dividerState; mCallback = callback; - - // Set the previous position ratio before minimized state after attaching this divider - if (mStableInsets.isEmpty()) { - WindowManagerWrapper.getInstance().getStableInsets(mStableInsets); - } + mTiles = tiles; + mSplitLayout = sdl; if (mState.mRatioPositionBeforeMinimized == 0) { // Set the middle target as the initial state - mSnapTargetBeforeMinimized = mSnapAlgorithm.getMiddleTarget(); + mSnapTargetBeforeMinimized = mSplitLayout.getSnapAlgorithm().getMiddleTarget(); } else { repositionSnapTargetBeforeMinimized(); } @@ -411,18 +407,34 @@ public class DividerView extends FrameLayout implements OnTouchListener, return mOtherTaskRect; } + private boolean inSplitMode() { + return getVisibility() == VISIBLE; + } + + /** Unlike setVisible, this directly hides the surface without changing view visibility. */ + void setHidden(boolean hidden) { + post(() -> { + if (!isViewSurfaceValid()) { + return; + } + Transaction t = mTiles.getTransaction(); + if (hidden) { + t.hide(getViewRootImpl().getSurfaceControl()); + } else { + t.show(getViewRootImpl().getSurfaceControl()); + } + t.apply(); + mTiles.releaseTransaction(t); + }); + } + public boolean startDragging(boolean animate, boolean touching) { cancelFlingAnimation(); if (touching) { mHandle.setTouching(true, animate); } - mDockSide = mWindowManagerProxy.getDockSide(); + mDockSide = mSplitLayout.getPrimarySplitSide(); - // Update snap algorithm if rotation has occurred - if (mDisplayRotation != mDefaultDisplay.getRotation()) { - updateDisplayInfo(); - } - initializeSnapAlgorithm(); mWindowManagerProxy.setResizing(true); if (touching) { mWindowManager.setSlippery(false); @@ -431,7 +443,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, if (mCallback != null) { mCallback.onDraggingStart(); } - return mDockSide != WindowManager.DOCKED_INVALID; + return inSplitMode(); } public void stopDragging(int position, float velocity, boolean avoidDismissStart, @@ -467,38 +479,22 @@ public class DividerView extends FrameLayout implements OnTouchListener, } private void updateDockSide() { - mDockSide = mWindowManagerProxy.getDockSide(); + mDockSide = mSplitLayout.getPrimarySplitSide(); mMinimizedShadow.setDockSide(mDockSide); } - private void initializeSnapAlgorithm() { - if (mSnapAlgorithm == null) { - mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth, - mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets, mDockSide); - if (mSnapTargetBeforeMinimized != null && mSnapTargetBeforeMinimized.isMiddleTarget) { - mSnapTargetBeforeMinimized = mSnapAlgorithm.getMiddleTarget(); - } - } - if (mMinimizedSnapAlgorithm == null) { - mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), - mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(), - mStableInsets, mDockSide, mDockedStackMinimized && mHomeStackResizable); - } - } - public DividerSnapAlgorithm getSnapAlgorithm() { - initializeSnapAlgorithm(); - return mDockedStackMinimized && mHomeStackResizable ? mMinimizedSnapAlgorithm : - mSnapAlgorithm; + return mDockedStackMinimized + && mHomeStackResizable ? mSplitLayout.getMinimizedSnapAlgorithm() + : mSplitLayout.getSnapAlgorithm(); } public int getCurrentPosition() { - getLocationOnScreen(mTempInt2); - if (isHorizontalDivision()) { - return mTempInt2[1] + mDividerInsets; - } else { - return mTempInt2[0] + mDividerInsets; - } + return isHorizontalDivision() ? mDividerPositionY : mDividerPositionX; + } + + public boolean isMinimized() { + return mDockedStackMinimized; } @Override @@ -557,25 +553,25 @@ public class DividerView extends FrameLayout implements OnTouchListener, } private void logResizeEvent(SnapTarget snapTarget) { - if (snapTarget == mSnapAlgorithm.getDismissStartTarget()) { + if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissStartTarget()) { MetricsLogger.action( mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide) ? LOG_VALUE_UNDOCK_MAX_OTHER : LOG_VALUE_UNDOCK_MAX_DOCKED); - } else if (snapTarget == mSnapAlgorithm.getDismissEndTarget()) { + } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissEndTarget()) { MetricsLogger.action( mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide) ? LOG_VALUE_UNDOCK_MAX_OTHER : LOG_VALUE_UNDOCK_MAX_DOCKED); - } else if (snapTarget == mSnapAlgorithm.getMiddleTarget()) { + } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getMiddleTarget()) { MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE, LOG_VALUE_RESIZE_50_50); - } else if (snapTarget == mSnapAlgorithm.getFirstSplitTarget()) { + } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getFirstSplitTarget()) { MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE, dockSideTopLeft(mDockSide) ? LOG_VALUE_RESIZE_DOCKED_SMALLER : LOG_VALUE_RESIZE_DOCKED_LARGER); - } else if (snapTarget == mSnapAlgorithm.getLastSplitTarget()) { + } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getLastSplitTarget()) { MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE, dockSideTopLeft(mDockSide) ? LOG_VALUE_RESIZE_DOCKED_LARGER @@ -625,12 +621,16 @@ public class DividerView extends FrameLayout implements OnTouchListener, : snapTarget.taskPosition, snapTarget)); Runnable endAction = () -> { - commitSnapFlags(snapTarget); + boolean dismissed = commitSnapFlags(snapTarget); mWindowManagerProxy.setResizing(false); updateDockSide(); mCurrentAnimator = null; mEntranceAnimationRunning = false; mExitAnimationRunning = false; + if (!dismissed) { + WindowManagerProxy.applyResizeSplits((mIsInMinimizeInteraction + ? mSnapTargetBeforeMinimized : snapTarget).position, mSplitLayout); + } if (mCallback != null) { mCallback.onDraggingEnd(); } @@ -642,12 +642,13 @@ public class DividerView extends FrameLayout implements OnTouchListener, // position isn't negative. final SnapTarget saveTarget; if (snapTarget.position < 0) { - saveTarget = mSnapAlgorithm.getMiddleTarget(); + saveTarget = mSplitLayout.getSnapAlgorithm().getMiddleTarget(); } else { saveTarget = snapTarget; } - if (saveTarget.position != mSnapAlgorithm.getDismissEndTarget().position - && saveTarget.position != mSnapAlgorithm.getDismissStartTarget().position) { + final DividerSnapAlgorithm snapAlgo = mSplitLayout.getSnapAlgorithm(); + if (saveTarget.position != snapAlgo.getDismissEndTarget().position + && saveTarget.position != snapAlgo.getDismissStartTarget().position) { saveSnapTargetBeforeMinimized(saveTarget); } } @@ -701,11 +702,11 @@ public class DividerView extends FrameLayout implements OnTouchListener, } } - private void commitSnapFlags(SnapTarget target) { + private boolean commitSnapFlags(SnapTarget target) { if (target.flag == SnapTarget.FLAG_NONE) { - return; + return false; } - boolean dismissOrMaximize; + final boolean dismissOrMaximize; if (target.flag == SnapTarget.FLAG_DISMISS_START) { dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT || mDockSide == WindowManager.DOCKED_TOP; @@ -713,12 +714,13 @@ public class DividerView extends FrameLayout implements OnTouchListener, dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT || mDockSide == WindowManager.DOCKED_BOTTOM; } - if (dismissOrMaximize) { - mWindowManagerProxy.dismissDockedStack(); - } else { - mWindowManagerProxy.maximizeDockedStack(); - } - mWindowManagerProxy.setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f); + mWindowManagerProxy.dismissOrMaximizeDocked(mTiles, dismissOrMaximize); + Transaction t = mTiles.getTransaction(); + setResizeDimLayer(t, true /* primary */, 0f); + setResizeDimLayer(t, false /* primary */, 0f); + t.apply(); + mTiles.releaseTransaction(t); + return true; } private void liftBackground() { @@ -765,6 +767,28 @@ public class DividerView extends FrameLayout implements OnTouchListener, mBackgroundLifted = false; } + private void initializeSurfaceState() { + int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; + // Recalculate the split-layout's internal tile bounds + mSplitLayout.resizeSplits(midPos); + Transaction t = mTiles.getTransaction(); + if (mDockedStackMinimized) { + int position = mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget().position; + calculateBoundsForPosition(position, mDockSide, mDockedRect); + calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide), + mOtherRect); + mDividerPositionX = mDividerPositionY = position; + resizeSplitSurfaces(t, mDockedRect, mSplitLayout.mPrimary, + mOtherRect, mSplitLayout.mSecondary); + } else { + resizeSplitSurfaces(t, mSplitLayout.mPrimary, null, + mSplitLayout.mSecondary, null); + } + setResizeDimLayer(t, true /* primary */, 0.f /* alpha */); + setResizeDimLayer(t, false /* secondary */, 0.f /* alpha */); + t.apply(); + mTiles.releaseTransaction(t); + } public void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable) { mHomeStackResizable = isHomeStackResizable; @@ -789,15 +813,11 @@ public class DividerView extends FrameLayout implements OnTouchListener, mDockedStackMinimized = minimized; } else if (mDockedStackMinimized != minimized) { mDockedStackMinimized = minimized; - if (mDisplayRotation != mDefaultDisplay.getRotation()) { + if (mSplitLayout.mDisplayLayout.rotation() != mDefaultDisplay.getRotation()) { // Splitscreen to minimize is about to starts after rotating landscape to seascape, // update insets, display info and snap algorithm targets WindowManagerWrapper.getInstance().getStableInsets(mStableInsets); repositionSnapTargetBeforeMinimized(); - updateDisplayInfo(); - } else { - mMinimizedSnapAlgorithm = null; - initializeSnapAlgorithm(); } if (mIsInMinimizeInteraction != minimized || mCurrentAnimator != null) { cancelFlingAnimation(); @@ -805,15 +825,51 @@ public class DividerView extends FrameLayout implements OnTouchListener, // Relayout to recalculate the divider shadow when minimizing requestLayout(); mIsInMinimizeInteraction = true; - resizeStack(mMinimizedSnapAlgorithm.getMiddleTarget()); + resizeStackSurfaces(mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget()); } else { - resizeStack(mSnapTargetBeforeMinimized); + resizeStackSurfaces(mSnapTargetBeforeMinimized); mIsInMinimizeInteraction = false; } } } } + void enterSplitMode(boolean isHomeStackResizable) { + post(() -> { + if (!isViewSurfaceValid()) { + return; + } + Transaction t = mTiles.getTransaction(); + t.show(getViewRootImpl().getSurfaceControl()).apply(); + mTiles.releaseTransaction(t); + }); + if (isHomeStackResizable) { + SnapTarget miniMid = mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget(); + if (mDockedStackMinimized) { + mDividerPositionY = mDividerPositionX = miniMid.position; + } + } + } + + private boolean isViewSurfaceValid() { + return getViewRootImpl() != null && getViewRootImpl().getSurfaceControl() != null + && getViewRootImpl().getSurfaceControl().isValid(); + } + + void exitSplitMode() { + // Reset tile bounds + post(() -> { + if (!isViewSurfaceValid()) { + return; + } + Transaction t = mTiles.getTransaction(); + t.hide(getViewRootImpl().getSurfaceControl()).apply(); + mTiles.releaseTransaction(t); + }); + int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; + WindowManagerProxy.applyResizeSplits(midPos, mSplitLayout); + } + public void setMinimizedDockStack(boolean minimized, long animDuration, boolean isHomeStackResizable) { mHomeStackResizable = isHomeStackResizable; @@ -844,14 +900,12 @@ public class DividerView extends FrameLayout implements OnTouchListener, mDockedStackMinimized = minimized; } else if (mDockedStackMinimized != minimized) { mIsInMinimizeInteraction = true; - mMinimizedSnapAlgorithm = null; mDockedStackMinimized = minimized; - initializeSnapAlgorithm(); stopDragging(minimized ? mSnapTargetBeforeMinimized.position : getCurrentPosition(), minimized - ? mMinimizedSnapAlgorithm.getMiddleTarget() + ? mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget() : mSnapTargetBeforeMinimized, animDuration, Interpolators.FAST_OUT_SLOW_IN, 0); setAdjustedForIme(false, animDuration); @@ -865,18 +919,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, .start(); } - public void setAdjustedForIme(boolean adjustedForIme) { - updateDockSide(); - mHandle.setAlpha(adjustedForIme ? 0f : 1f); - if (!adjustedForIme) { - resetBackground(); - } else if (mDockSide == WindowManager.DOCKED_TOP) { - mBackground.setPivotY(0); - mBackground.setScaleY(ADJUSTED_FOR_IME_SCALE); - } - mAdjustedForIme = adjustedForIme; - } - public void setAdjustedForIme(boolean adjustedForIme, long animDuration) { updateDockSide(); mHandle.animate() @@ -902,7 +944,8 @@ public class DividerView extends FrameLayout implements OnTouchListener, private void saveSnapTargetBeforeMinimized(SnapTarget target) { mSnapTargetBeforeMinimized = target; mState.mRatioPositionBeforeMinimized = (float) target.position / - (isHorizontalDivision() ? mDisplayHeight : mDisplayWidth); + (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height() + : mSplitLayout.mDisplayLayout.width()); } private void resetBackground() { @@ -916,51 +959,17 @@ public class DividerView extends FrameLayout implements OnTouchListener, @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - updateDisplayInfo(); - } - - public void notifyDockSideChanged(int newDockSide) { - int oldDockSide = mDockSide; - mDockSide = newDockSide; - mMinimizedShadow.setDockSide(mDockSide); - requestLayout(); - - // Update the snap position to the new docked side with correct insets - WindowManagerWrapper.getInstance().getStableInsets(mStableInsets); - mMinimizedSnapAlgorithm = null; - initializeSnapAlgorithm(); - - if (oldDockSide == DOCKED_LEFT && mDockSide == DOCKED_RIGHT - || oldDockSide == DOCKED_RIGHT && mDockSide == DOCKED_LEFT) { - repositionSnapTargetBeforeMinimized(); - } - - // Landscape to seascape rotation requires minimized to resize docked app correctly - if (mHomeStackResizable && mDockedStackMinimized) { - resizeStack(mMinimizedSnapAlgorithm.getMiddleTarget()); - } } private void repositionSnapTargetBeforeMinimized() { int position = (int) (mState.mRatioPositionBeforeMinimized * - (isHorizontalDivision() ? mDisplayHeight : mDisplayWidth)); - mSnapAlgorithm = null; - initializeSnapAlgorithm(); + (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height() + : mSplitLayout.mDisplayLayout.width())); // Set the snap target before minimized but do not save until divider is attached and not // minimized because it does not know its minimized state yet. - mSnapTargetBeforeMinimized = mSnapAlgorithm.calculateNonDismissingSnapTarget(position); - } - - private void updateDisplayInfo() { - mDisplayRotation = mDefaultDisplay.getRotation(); - final DisplayInfo info = new DisplayInfo(); - mDefaultDisplay.getDisplayInfo(info); - mDisplayWidth = info.logicalWidth; - mDisplayHeight = info.logicalHeight; - mSnapAlgorithm = null; - mMinimizedSnapAlgorithm = null; - initializeSnapAlgorithm(); + mSnapTargetBeforeMinimized = + mSplitLayout.getSnapAlgorithm().calculateNonDismissingSnapTarget(position); } private int calculatePosition(int touchX, int touchY) { @@ -994,8 +1003,9 @@ public class DividerView extends FrameLayout implements OnTouchListener, } public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) { - DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth, - mDisplayHeight, mDividerSize); + DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, + mSplitLayout.mDisplayLayout.width(), mSplitLayout.mDisplayLayout.height(), + mDividerSize); } public void resizeStackDelayed(int position, int taskPosition, SnapTarget taskSnapTarget) { @@ -1005,16 +1015,61 @@ public class DividerView extends FrameLayout implements OnTouchListener, mSfChoreographer.scheduleAtSfVsync(mHandler, message); } - private void resizeStack(SnapTarget taskSnapTarget) { - resizeStack(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget); + private void resizeStackSurfaces(SnapTarget taskSnapTarget) { + resizeStackSurfaces(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget); + } + + void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect otherRect) { + post(() -> resizeSplitSurfaces(t, dockedRect, null, otherRect, null)); + } + + private void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect dockedTaskRect, + Rect otherRect, Rect otherTaskRect) { + dockedTaskRect = dockedTaskRect == null ? dockedRect : dockedTaskRect; + otherTaskRect = otherTaskRect == null ? otherRect : otherTaskRect; + + mDividerPositionX = dockedRect.right; + mDividerPositionY = dockedRect.bottom; + + t.setPosition(mTiles.mPrimarySurface, dockedTaskRect.left, dockedTaskRect.top); + Rect crop = new Rect(dockedRect); + crop.offsetTo(-Math.min(dockedTaskRect.left - dockedRect.left, 0), + -Math.min(dockedTaskRect.top - dockedRect.top, 0)); + t.setWindowCrop(mTiles.mPrimarySurface, crop); + t.setPosition(mTiles.mSecondarySurface, otherTaskRect.left, otherTaskRect.top); + crop.set(otherRect); + crop.offsetTo(-(otherTaskRect.left - otherRect.left), + -(otherTaskRect.top - otherRect.top)); + t.setWindowCrop(mTiles.mSecondarySurface, crop); + SurfaceControl dividerCtrl = getViewRootImpl() != null + ? getViewRootImpl().getSurfaceControl() : null; + if (dividerCtrl != null && dividerCtrl.isValid()) { + if (isHorizontalDivision()) { + t.setPosition(dividerCtrl, 0, mDividerPositionY - mDividerInsets); + } else { + t.setPosition(dividerCtrl, mDividerPositionX - mDividerInsets, 0); + } + } + } + + void setResizeDimLayer(Transaction t, boolean primary, float alpha) { + SurfaceControl dim = primary ? mTiles.mPrimaryDim : mTiles.mSecondaryDim; + if (alpha <= 0.f) { + t.hide(dim); + } else { + t.setAlpha(dim, alpha); + t.show(dim); + } } - public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) { + void resizeStackSurfaces(int position, int taskPosition, SnapTarget taskSnapTarget) { if (mRemoved) { // This divider view has been removed so shouldn't have any additional influence. return; } calculateBoundsForPosition(position, mDockSide, mDockedRect); + calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide), + mOtherRect); if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) { return; @@ -1025,6 +1080,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, mBackground.invalidate(); } + Transaction t = mTiles.getTransaction(); mLastResizeRect.set(mDockedRect); if (mHomeStackResizable && mIsInMinimizeInteraction) { calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, mDockSide, @@ -1037,8 +1093,10 @@ public class DividerView extends FrameLayout implements OnTouchListener, mDockedTaskRect.offset(Math.max(position, mStableInsets.left - mDividerSize) - mDockedTaskRect.left + mDividerSize, 0); } - mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedTaskRect, - mOtherTaskRect, null); + resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, + mOtherTaskRect); + t.apply(); + mTiles.releaseTransaction(t); return; } @@ -1052,8 +1110,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, } calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect); - mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null, - mOtherTaskRect, null); + resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect); } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) { calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect); mDockedInsetRect.set(mDockedTaskRect); @@ -1066,8 +1123,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, if (mDockSide == DOCKED_RIGHT) { mDockedTaskRect.offset(position - mStableInsets.left + mDividerSize, 0); } - mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect, - mOtherTaskRect, mOtherInsetRect); + resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect); } else if (taskPosition != TASK_POSITION_SAME) { calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide), mOtherRect); @@ -1078,7 +1134,8 @@ public class DividerView extends FrameLayout implements OnTouchListener, restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget); calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect); calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect); - mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight); + mTmpRect.set(0, 0, mSplitLayout.mDisplayLayout.width(), + mSplitLayout.mDisplayLayout.height()); alignTopLeft(mDockedRect, mDockedTaskRect); alignTopLeft(mOtherRect, mOtherTaskRect); mDockedInsetRect.set(mDockedTaskRect); @@ -1094,15 +1151,15 @@ public class DividerView extends FrameLayout implements OnTouchListener, taskPositionDocked); applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position, taskPositionOther); - mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect, - mOtherTaskRect, mOtherInsetRect); + resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect); } else { - mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null); + resizeSplitSurfaces(t, mDockedRect, null, mOtherRect, null); } SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position); float dimFraction = getDimFraction(position, closestDismissTarget); - mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f, - getWindowingModeForDismissTarget(closestDismissTarget), dimFraction); + setResizeDimLayer(t, isDismissTargetPrimary(closestDismissTarget), dimFraction); + t.apply(); + mTiles.releaseTransaction(t); } private void applyExitAnimationParallax(Rect taskRect, int position) { @@ -1156,10 +1213,12 @@ public class DividerView extends FrameLayout implements OnTouchListener, private int restrictDismissingTaskPosition(int taskPosition, int dockSide, SnapTarget snapTarget) { if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) { - return Math.max(mSnapAlgorithm.getFirstSplitTarget().position, mStartPosition); + return Math.max(mSplitLayout.getSnapAlgorithm().getFirstSplitTarget().position, + mStartPosition); } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END && dockSideBottomRight(dockSide)) { - return Math.min(mSnapAlgorithm.getLastSplitTarget().position, mStartPosition); + return Math.min(mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position, + mStartPosition); } else { return taskPosition; } @@ -1171,19 +1230,19 @@ public class DividerView extends FrameLayout implements OnTouchListener, private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget, int position, int taskPosition) { float fraction = Math.min(1, Math.max(0, - mSnapAlgorithm.calculateDismissingFraction(position))); + mSplitLayout.getSnapAlgorithm().calculateDismissingFraction(position))); SnapTarget dismissTarget = null; SnapTarget splitTarget = null; int start = 0; - if (position <= mSnapAlgorithm.getLastSplitTarget().position + if (position <= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position && dockSideTopLeft(dockSide)) { - dismissTarget = mSnapAlgorithm.getDismissStartTarget(); - splitTarget = mSnapAlgorithm.getFirstSplitTarget(); + dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissStartTarget(); + splitTarget = mSplitLayout.getSnapAlgorithm().getFirstSplitTarget(); start = taskPosition; - } else if (position >= mSnapAlgorithm.getLastSplitTarget().position + } else if (position >= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position && dockSideBottomRight(dockSide)) { - dismissTarget = mSnapAlgorithm.getDismissEndTarget(); - splitTarget = mSnapAlgorithm.getLastSplitTarget(); + dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissEndTarget(); + splitTarget = mSplitLayout.getSnapAlgorithm().getLastSplitTarget(); start = splitTarget.position; } if (dismissTarget != null && fraction > 0f @@ -1236,14 +1295,10 @@ public class DividerView extends FrameLayout implements OnTouchListener, } } - private int getWindowingModeForDismissTarget(SnapTarget dismissTarget) { - if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide)) + private boolean isDismissTargetPrimary(SnapTarget dismissTarget) { + return (dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide)) || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END - && dockSideBottomRight(mDockSide))) { - return WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; - } else { - return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - } + && dockSideBottomRight(mDockSide)); } /** @@ -1297,7 +1352,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, } void onDockedFirstAnimationFrame() { - saveSnapTargetBeforeMinimized(mSnapAlgorithm.getMiddleTarget()); + saveSnapTargetBeforeMinimized(mSplitLayout.getSnapAlgorithm().getMiddleTarget()); } void onDockedTopTask() { @@ -1307,8 +1362,9 @@ public class DividerView extends FrameLayout implements OnTouchListener, updateDockSide(); mEntranceAnimationRunning = true; - resizeStack(calculatePositionForInsetBounds(), mSnapAlgorithm.getMiddleTarget().position, - mSnapAlgorithm.getMiddleTarget()); + resizeStackSurfaces(calculatePositionForInsetBounds(), + mSplitLayout.getSnapAlgorithm().getMiddleTarget().position, + mSplitLayout.getSnapAlgorithm().getMiddleTarget()); } void onRecentsDrawn() { @@ -1337,13 +1393,12 @@ public class DividerView extends FrameLayout implements OnTouchListener, } void onUndockingTask() { - int dockSide = mWindowManagerProxy.getDockSide(); - if (dockSide != WindowManager.DOCKED_INVALID && (mHomeStackResizable - || !mDockedStackMinimized)) { + int dockSide = mSplitLayout.getPrimarySplitSide(); + if (inSplitMode() && (mHomeStackResizable || !mDockedStackMinimized)) { startDragging(false /* animate */, false /* touching */); SnapTarget target = dockSideTopLeft(dockSide) - ? mSnapAlgorithm.getDismissEndTarget() - : mSnapAlgorithm.getDismissStartTarget(); + ? mSplitLayout.getSnapAlgorithm().getDismissEndTarget() + : mSplitLayout.getSnapAlgorithm().getDismissStartTarget(); // Don't start immediately - give a little bit time to settle the drag resize change. mExitAnimationRunning = true; @@ -1354,8 +1409,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, } private int calculatePositionForInsetBounds() { - mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight); - mTmpRect.inset(mStableInsets); + mSplitLayout.mDisplayLayout.getStableBounds(mTmpRect); return DockedDividerUtils.calculatePositionForBounds(mTmpRect, mDockSide, mDividerSize); } } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java index 2486d6534e8d..bd843b0e0f9e 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java @@ -26,12 +26,13 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; -import android.content.Context; import android.graphics.PixelFormat; import android.os.Binder; import android.view.View; import android.view.WindowManager; +import com.android.systemui.wm.SystemWindows; + /** * Manages the window parameters of the docked stack divider. */ @@ -39,15 +40,16 @@ public class DividerWindowManager { private static final String WINDOW_TITLE = "DockedStackDivider"; - private final WindowManager mWindowManager; + private final SystemWindows mSystemWindows; private WindowManager.LayoutParams mLp; private View mView; - public DividerWindowManager(Context ctx) { - mWindowManager = ctx.getSystemService(WindowManager.class); + public DividerWindowManager(SystemWindows systemWindows) { + mSystemWindows = systemWindows; } - public void add(View view, int width, int height) { + /** Add a divider view */ + public void add(View view, int width, int height, int displayId) { mLp = new WindowManager.LayoutParams( width, height, TYPE_DOCK_DIVIDER, FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL @@ -60,13 +62,13 @@ public class DividerWindowManager { view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - mWindowManager.addView(view, mLp); + mSystemWindows.addView(view, mLp, displayId, TYPE_DOCK_DIVIDER); mView = view; } public void remove() { if (mView != null) { - mWindowManager.removeView(mView); + mSystemWindows.removeView(mView); } mView = null; } @@ -81,7 +83,7 @@ public class DividerWindowManager { changed = true; } if (changed) { - mWindowManager.updateViewLayout(mView, mLp); + mSystemWindows.updateViewLayout(mView, mLp); } } @@ -95,7 +97,7 @@ public class DividerWindowManager { changed = true; } if (changed) { - mWindowManager.updateViewLayout(mView, mLp); + mSystemWindows.updateViewLayout(mView, mLp); } } } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java index c6ac309a0f4e..db7996eed7f0 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java @@ -31,6 +31,8 @@ import com.android.systemui.R; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; +import java.util.function.Consumer; + /** * Controller that decides when to show the {@link ForcedResizableInfoActivity}. */ @@ -52,6 +54,12 @@ public class ForcedResizableInfoActivityController { } }; + private final Consumer<Boolean> mDockedStackExistsListener = exists -> { + if (!exists) { + mPackagesShownInSession.clear(); + } + }; + /** Record of force resized task that's pending to be handled. */ private class PendingTaskRecord { int taskId; @@ -67,7 +75,7 @@ public class ForcedResizableInfoActivityController { } } - public ForcedResizableInfoActivityController(Context context) { + public ForcedResizableInfoActivityController(Context context, Divider divider) { mContext = context; ActivityManagerWrapper.getInstance().registerTaskStackListener( new TaskStackChangeListener() { @@ -87,12 +95,7 @@ public class ForcedResizableInfoActivityController { activityLaunchOnSecondaryDisplayFailed(); } }); - } - - public void notifyDockedStackExistsChanged(boolean exists) { - if (!exists) { - mPackagesShownInSession.clear(); - } + divider.registerInSplitScreenListener(mDockedStackExistsListener); } public void onAppTransitionFinished() { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java new file mode 100644 index 000000000000..b19f560f2f50 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java @@ -0,0 +1,310 @@ +/* + * 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 static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.WindowManager.DOCKED_BOTTOM; +import static android.view.WindowManager.DOCKED_INVALID; +import static android.view.WindowManager.DOCKED_LEFT; +import static android.view.WindowManager.DOCKED_RIGHT; +import static android.view.WindowManager.DOCKED_TOP; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Rect; +import android.util.TypedValue; +import android.view.WindowContainerTransaction; + +import com.android.internal.policy.DividerSnapAlgorithm; +import com.android.internal.policy.DockedDividerUtils; +import com.android.systemui.wm.DisplayLayout; + +/** + * Handles split-screen related internal display layout. In general, this represents the + * WM-facing understanding of the splits. + */ +public class SplitDisplayLayout { + /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to + * restrict IME adjustment so that a min portion of top stack remains visible.*/ + private static final float ADJUSTED_STACK_FRACTION_MIN = 0.3f; + + private static final int DIVIDER_WIDTH_INACTIVE_DP = 4; + + SplitScreenTaskOrganizer mTiles; + DisplayLayout mDisplayLayout; + Context mContext; + + // Lazy stuff + boolean mResourcesValid = false; + int mDividerSize; + int mDividerSizeInactive; + private DividerSnapAlgorithm mSnapAlgorithm = null; + private DividerSnapAlgorithm mMinimizedSnapAlgorithm = null; + Rect mPrimary = null; + Rect mSecondary = null; + Rect mAdjustedPrimary = null; + Rect mAdjustedSecondary = null; + + public SplitDisplayLayout(Context ctx, DisplayLayout dl, SplitScreenTaskOrganizer taskTiles) { + mTiles = taskTiles; + mDisplayLayout = dl; + mContext = ctx; + } + + void rotateTo(int newRotation) { + mDisplayLayout.rotateTo(mContext.getResources(), newRotation); + final Configuration config = new Configuration(); + config.unset(); + config.orientation = mDisplayLayout.getOrientation(); + Rect tmpRect = new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height()); + tmpRect.inset(mDisplayLayout.nonDecorInsets()); + config.windowConfiguration.setAppBounds(tmpRect); + tmpRect.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height()); + tmpRect.inset(mDisplayLayout.stableInsets()); + config.screenWidthDp = (int) (tmpRect.width() / mDisplayLayout.density()); + config.screenHeightDp = (int) (tmpRect.height() / mDisplayLayout.density()); + mContext = mContext.createConfigurationContext(config); + mSnapAlgorithm = null; + mMinimizedSnapAlgorithm = null; + mResourcesValid = false; + } + + private void updateResources() { + if (mResourcesValid) { + return; + } + mResourcesValid = true; + Resources res = mContext.getResources(); + mDividerSize = DockedDividerUtils.getDividerSize(res, + DockedDividerUtils.getDividerInsets(res)); + mDividerSizeInactive = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, DIVIDER_WIDTH_INACTIVE_DP, res.getDisplayMetrics()); + } + + int getPrimarySplitSide() { + return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP; + } + + boolean isMinimized() { + return mTiles.mSecondary.topActivityType == ACTIVITY_TYPE_HOME + || mTiles.mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS; + } + + DividerSnapAlgorithm getSnapAlgorithm() { + if (mSnapAlgorithm == null) { + updateResources(); + boolean isHorizontalDivision = !mDisplayLayout.isLandscape(); + mSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(), + mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize, + isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide()); + } + return mSnapAlgorithm; + } + + DividerSnapAlgorithm getMinimizedSnapAlgorithm() { + if (mMinimizedSnapAlgorithm == null) { + updateResources(); + boolean isHorizontalDivision = !mDisplayLayout.isLandscape(); + mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(), + mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize, + isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide(), + true /* isMinimized */); + } + return mMinimizedSnapAlgorithm; + } + + void resizeSplits(int position) { + mPrimary = mPrimary == null ? new Rect() : mPrimary; + mSecondary = mSecondary == null ? new Rect() : mSecondary; + calcSplitBounds(position, mPrimary, mSecondary); + } + + void resizeSplits(int position, WindowContainerTransaction t) { + resizeSplits(position); + t.setBounds(mTiles.mPrimary.token, mPrimary); + t.setBounds(mTiles.mSecondary.token, mSecondary); + + t.setSmallestScreenWidthDp(mTiles.mPrimary.token, + getSmallestWidthDpForBounds(mContext, mDisplayLayout, mPrimary)); + t.setSmallestScreenWidthDp(mTiles.mSecondary.token, + getSmallestWidthDpForBounds(mContext, mDisplayLayout, mSecondary)); + } + + void calcSplitBounds(int position, @NonNull Rect outPrimary, @NonNull Rect outSecondary) { + int dockSide = getPrimarySplitSide(); + DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outPrimary, + mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize); + + DockedDividerUtils.calculateBoundsForPosition(position, + DockedDividerUtils.invertDockSide(dockSide), outSecondary, mDisplayLayout.width(), + mDisplayLayout.height(), mDividerSize); + } + + Rect calcMinimizedHomeStackBounds() { + DividerSnapAlgorithm.SnapTarget miniMid = getMinimizedSnapAlgorithm().getMiddleTarget(); + Rect homeBounds = new Rect(); + DockedDividerUtils.calculateBoundsForPosition(miniMid.position, + DockedDividerUtils.invertDockSide(getPrimarySplitSide()), homeBounds, + mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize); + return homeBounds; + } + + /** + * Updates the adjustment depending on it's current state. + */ + void updateAdjustedBounds(int currImeTop, int startTop, int finalTop) { + updateAdjustedBounds(mDisplayLayout, currImeTop, startTop, finalTop, mDividerSize, + mDividerSizeInactive, mPrimary, mSecondary); + } + + /** + * Updates the adjustment depending on it's current state. + */ + private void updateAdjustedBounds(DisplayLayout dl, int currImeTop, int startTop, int finalTop, + int dividerWidth, int dividerWidthInactive, Rect primaryBounds, Rect secondaryBounds) { + adjustForIME(dl, currImeTop, startTop, finalTop, dividerWidth, dividerWidthInactive, + primaryBounds, secondaryBounds); + } + + /** Assumes top/bottom split. Splits are not adjusted for left/right splits. */ + private void adjustForIME(DisplayLayout dl, int currImeTop, int startTop, int finalTop, + int dividerWidth, int dividerWidthInactive, Rect primaryBounds, Rect secondaryBounds) { + if (mAdjustedPrimary == null) { + mAdjustedPrimary = new Rect(); + mAdjustedSecondary = new Rect(); + } + + final Rect displayStableRect = new Rect(); + dl.getStableBounds(displayStableRect); + + final boolean showing = finalTop < startTop; + final float progress = ((float) (currImeTop - startTop)) / (finalTop - startTop); + final float dividerSquish = showing ? progress : 1.f - progress; + final int currDividerWidth = + (int) (dividerWidthInactive * dividerSquish + dividerWidth * (1.f - dividerSquish)); + + final int minTopStackBottom = displayStableRect.top + + (int) ((mPrimary.bottom - displayStableRect.top) * ADJUSTED_STACK_FRACTION_MIN); + final int minImeTop = minTopStackBottom + currDividerWidth; + + // Calculate an offset which shifts the stacks up by the height of the IME, but still + // leaves at least 30% of the top stack visible. + final int yOffset = Math.max(0, dl.height() - Math.max(currImeTop, minImeTop)); + + // TOP + // Reduce the offset by an additional small amount to squish the divider bar. + mAdjustedPrimary.set(primaryBounds); + mAdjustedPrimary.offset(0, -yOffset + (dividerWidth - currDividerWidth)); + + // BOTTOM + mAdjustedSecondary.set(secondaryBounds); + mAdjustedSecondary.offset(0, -yOffset); + } + + static int getSmallestWidthDpForBounds(@NonNull Context context, DisplayLayout dl, + Rect bounds) { + int dividerSize = DockedDividerUtils.getDividerSize(context.getResources(), + DockedDividerUtils.getDividerInsets(context.getResources())); + + int minWidth = Integer.MAX_VALUE; + + // Go through all screen orientations and find the orientation in which the task has the + // smallest width. + Rect tmpRect = new Rect(); + Rect rotatedDisplayRect = new Rect(); + Rect displayRect = new Rect(0, 0, dl.width(), dl.height()); + + DisplayLayout tmpDL = new DisplayLayout(); + for (int rotation = 0; rotation < 4; rotation++) { + tmpDL.set(dl); + tmpDL.rotateTo(context.getResources(), rotation); + DividerSnapAlgorithm snap = initSnapAlgorithmForRotation(context, tmpDL, dividerSize); + + tmpRect.set(bounds); + DisplayLayout.rotateBounds(tmpRect, displayRect, rotation - dl.rotation()); + rotatedDisplayRect.set(0, 0, tmpDL.width(), tmpDL.height()); + final int dockSide = getPrimarySplitSide(tmpRect, rotatedDisplayRect, + tmpDL.getOrientation()); + final int position = DockedDividerUtils.calculatePositionForBounds(tmpRect, dockSide, + dividerSize); + + final int snappedPosition = + snap.calculateNonDismissingSnapTarget(position).position; + DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, tmpRect, + tmpDL.width(), tmpDL.height(), dividerSize); + Rect insettedDisplay = new Rect(rotatedDisplayRect); + insettedDisplay.inset(tmpDL.stableInsets()); + tmpRect.intersect(insettedDisplay); + minWidth = Math.min(tmpRect.width(), minWidth); + } + return (int) (minWidth / dl.density()); + } + + static DividerSnapAlgorithm initSnapAlgorithmForRotation(Context context, DisplayLayout dl, + int dividerSize) { + final Configuration config = new Configuration(); + config.unset(); + config.orientation = dl.getOrientation(); + Rect tmpRect = new Rect(0, 0, dl.width(), dl.height()); + tmpRect.inset(dl.nonDecorInsets()); + config.windowConfiguration.setAppBounds(tmpRect); + tmpRect.set(0, 0, dl.width(), dl.height()); + tmpRect.inset(dl.stableInsets()); + config.screenWidthDp = (int) (tmpRect.width() / dl.density()); + config.screenHeightDp = (int) (tmpRect.height() / dl.density()); + final Context rotationContext = context.createConfigurationContext(config); + return new DividerSnapAlgorithm( + rotationContext.getResources(), dl.width(), dl.height(), dividerSize, + config.orientation == ORIENTATION_PORTRAIT, dl.stableInsets()); + } + + /** + * Get the current primary-split side. Determined by its location of {@param bounds} within + * {@param displayRect} but if both are the same, it will try to dock to each side and determine + * if allowed in its respected {@param orientation}. + * + * @param bounds bounds of the primary split task to get which side is docked + * @param displayRect bounds of the display that contains the primary split task + * @param orientation the origination of device + * @return current primary-split side + */ + static int getPrimarySplitSide(Rect bounds, Rect displayRect, int orientation) { + if (orientation == ORIENTATION_PORTRAIT) { + // Portrait mode, docked either at the top or the bottom. + final int diff = (displayRect.bottom - bounds.bottom) - (bounds.top - displayRect.top); + if (diff < 0) { + return DOCKED_BOTTOM; + } else { + // Top is default + return DOCKED_TOP; + } + } else if (orientation == ORIENTATION_LANDSCAPE) { + // Landscape mode, docked either on the left or on the right. + final int diff = (displayRect.right - bounds.right) - (bounds.left - displayRect.left); + if (diff < 0) { + return DOCKED_RIGHT; + } + return DOCKED_LEFT; + } + return DOCKED_INVALID; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java new file mode 100644 index 000000000000..0a54d2f7d8c9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java @@ -0,0 +1,167 @@ +/* + * 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 static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.view.Display.DEFAULT_DISPLAY; + +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ITaskOrganizerController; +import android.app.WindowConfiguration; +import android.os.RemoteException; +import android.util.Log; +import android.util.Pools; +import android.view.Display; +import android.view.ITaskOrganizer; +import android.view.IWindowContainer; +import android.view.SurfaceControl; +import android.view.SurfaceSession; + +class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub { + private static final String TAG = "SplitScreenTaskOrganizer"; + private static final boolean DEBUG = Divider.DEBUG; + + RunningTaskInfo mPrimary; + RunningTaskInfo mSecondary; + SurfaceControl mPrimarySurface; + SurfaceControl mSecondarySurface; + SurfaceControl mPrimaryDim; + SurfaceControl mSecondaryDim; + final Divider mDivider; + + private final Pools.SynchronizedPool<SurfaceControl.Transaction> mTransactionPool = + new Pools.SynchronizedPool<>(4); + + SplitScreenTaskOrganizer(Divider divider) { + mDivider = divider; + } + + void init(ITaskOrganizerController organizerController, SurfaceSession session) + throws RemoteException { + organizerController.registerTaskOrganizer(this, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + organizerController.registerTaskOrganizer(this, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + mPrimary = organizerController.createRootTask(Display.DEFAULT_DISPLAY, + WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + mSecondary = organizerController.createRootTask(Display.DEFAULT_DISPLAY, + WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + mPrimarySurface = mPrimary.token.getLeash(); + mSecondarySurface = mSecondary.token.getLeash(); + + // Initialize dim surfaces: + mPrimaryDim = new SurfaceControl.Builder(session).setParent(mPrimarySurface) + .setColorLayer().setName("Primary Divider Dim").build(); + mSecondaryDim = new SurfaceControl.Builder(session).setParent(mSecondarySurface) + .setColorLayer().setName("Secondary Divider Dim").build(); + SurfaceControl.Transaction t = getTransaction(); + t.setLayer(mPrimaryDim, Integer.MAX_VALUE); + t.setColor(mPrimaryDim, new float[]{0f, 0f, 0f}); + t.setLayer(mSecondaryDim, Integer.MAX_VALUE); + t.setColor(mSecondaryDim, new float[]{0f, 0f, 0f}); + t.apply(); + releaseTransaction(t); + } + + SurfaceControl.Transaction getTransaction() { + SurfaceControl.Transaction t = mTransactionPool.acquire(); + if (t == null) { + return new SurfaceControl.Transaction(); + } + return t; + } + + void releaseTransaction(SurfaceControl.Transaction t) { + mTransactionPool.release(t); + } + + @Override + public void taskAppeared(RunningTaskInfo taskInfo) { + } + + @Override + public void taskVanished(IWindowContainer container) { + } + + @Override + public void transactionReady(int id, SurfaceControl.Transaction t) { + } + + @Override + public void onTaskInfoChanged(RunningTaskInfo taskInfo) { + if (taskInfo.displayId != DEFAULT_DISPLAY) { + return; + } + mDivider.getHandler().post(() -> handleTaskInfoChanged(taskInfo)); + } + + /** + * This is effectively a finite state machine which moves between the various split-screen + * presentations based on the contents of the split regions. + */ + private void handleTaskInfoChanged(RunningTaskInfo info) { + final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED; + final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED; + if (info.token.asBinder() == mPrimary.token.asBinder()) { + mPrimary = info; + } else if (info.token.asBinder() == mSecondary.token.asBinder()) { + mSecondary = info; + } + final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED; + final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED; + if (DEBUG) { + Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary); + } + if (primaryIsEmpty || secondaryIsEmpty) { + // At-least one of the splits is empty which means we are currently transitioning + // into or out-of split-screen mode. + if (DEBUG) { + Log.d(TAG, " at-least one split empty " + mPrimary.topActivityType + + " " + mSecondary.topActivityType); + } + if (mDivider.inSplitMode()) { + // Was in split-mode, which means we are leaving split, so continue that. + // This happens when the stack in the primary-split is dismissed. + if (DEBUG) { + Log.d(TAG, " was in split, so this means leave it " + + mPrimary.topActivityType + " " + mSecondary.topActivityType); + } + WindowManagerProxy.applyDismissSplit(this, true /* dismissOrMaximize */); + mDivider.updateVisibility(false /* visible */); + } 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. + // This just tells window-manager to reparent things, the UI will respond + // when it gets new task info for the secondary split. + if (DEBUG) { + Log.d(TAG, " was not in split, but primary is populated, so enter it"); + } + mDivider.startEnterSplit(); + } + } else if (mSecondary.topActivityType == ACTIVITY_TYPE_HOME + || mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS) { + // Both splits are populated but the secondary split has a home/recents stack on top, + // so enter minimized mode. + mDivider.ensureMinimizedSplit(); + } else { + // Both splits are populated by normal activities, so make sure we aren't minimized. + mDivider.ensureNormalSplit(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java index 228aab5e3eec..76857337f73e 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java @@ -16,16 +16,25 @@ package com.android.systemui.stackdivider; -import static android.view.WindowManager.DOCKED_INVALID; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.Display.DEFAULT_DISPLAY; +import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.graphics.Rect; import android.os.RemoteException; import android.util.Log; +import android.view.Display; +import android.view.IWindowContainer; +import android.view.WindowContainerTransaction; import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -35,88 +44,20 @@ import java.util.concurrent.Executors; 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(); - private final Rect mTempDockedTaskRect = new Rect(); - private final Rect mTempDockedInsetRect = new Rect(); - private final Rect mTempOtherTaskRect = new Rect(); - private final Rect mTempOtherInsetRect = new Rect(); private final Rect mTmpRect1 = new Rect(); - private final Rect mTmpRect2 = new Rect(); - private final Rect mTmpRect3 = new Rect(); - private final Rect mTmpRect4 = new Rect(); - private final Rect mTmpRect5 = new Rect(); @GuardedBy("mDockedRect") private final Rect mTouchableRegion = new Rect(); - private boolean mDimLayerVisible; - private int mDimLayerTargetWindowingMode; - private float mDimLayerAlpha; - private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); - private final Runnable mResizeRunnable = new Runnable() { - @Override - public void run() { - synchronized (mDockedRect) { - mTmpRect1.set(mDockedRect); - mTmpRect2.set(mTempDockedTaskRect); - mTmpRect3.set(mTempDockedInsetRect); - mTmpRect4.set(mTempOtherTaskRect); - mTmpRect5.set(mTempOtherInsetRect); - } - try { - ActivityTaskManager.getService() - .resizeDockedStack(mTmpRect1, - mTmpRect2.isEmpty() ? null : mTmpRect2, - mTmpRect3.isEmpty() ? null : mTmpRect3, - mTmpRect4.isEmpty() ? null : mTmpRect4, - mTmpRect5.isEmpty() ? null : mTmpRect5); - } catch (RemoteException e) { - Log.w(TAG, "Failed to resize stack: " + e); - } - } - }; - - private final Runnable mDismissRunnable = new Runnable() { - @Override - public void run() { - try { - ActivityTaskManager.getService().dismissSplitScreenMode(false /* onTop */); - } catch (RemoteException e) { - Log.w(TAG, "Failed to remove stack: " + e); - } - } - }; - - private final Runnable mMaximizeRunnable = new Runnable() { - @Override - public void run() { - try { - ActivityTaskManager.getService().dismissSplitScreenMode(true /* onTop */); - } catch (RemoteException e) { - Log.w(TAG, "Failed to resize stack: " + e); - } - } - }; - - private final Runnable mDimLayerRunnable = new Runnable() { - @Override - public void run() { - try { - WindowManagerGlobal.getWindowManagerService().setResizeDimLayer(mDimLayerVisible, - mDimLayerTargetWindowingMode, mDimLayerAlpha); - } catch (RemoteException e) { - Log.w(TAG, "Failed to resize stack: " + e); - } - } - }; - private final Runnable mSetTouchableRegionRunnable = new Runnable() { @Override public void run() { @@ -139,40 +80,9 @@ public class WindowManagerProxy { return sInstance; } - public void resizeDockedStack(Rect docked, Rect tempDockedTaskRect, Rect tempDockedInsetRect, - Rect tempOtherTaskRect, Rect tempOtherInsetRect) { - synchronized (mDockedRect) { - mDockedRect.set(docked); - if (tempDockedTaskRect != null) { - mTempDockedTaskRect.set(tempDockedTaskRect); - } else { - mTempDockedTaskRect.setEmpty(); - } - if (tempDockedInsetRect != null) { - mTempDockedInsetRect.set(tempDockedInsetRect); - } else { - mTempDockedInsetRect.setEmpty(); - } - if (tempOtherTaskRect != null) { - mTempOtherTaskRect.set(tempOtherTaskRect); - } else { - mTempOtherTaskRect.setEmpty(); - } - if (tempOtherInsetRect != null) { - mTempOtherInsetRect.set(tempOtherInsetRect); - } else { - mTempOtherInsetRect.setEmpty(); - } - } - mExecutor.execute(mResizeRunnable); - } - - public void dismissDockedStack() { - mExecutor.execute(mDismissRunnable); - } - - public void maximizeDockedStack() { - mExecutor.execute(mMaximizeRunnable); + void dismissOrMaximizeDocked( + final SplitScreenTaskOrganizer tiles, final boolean dismissOrMaximize) { + mExecutor.execute(() -> applyDismissSplit(tiles, dismissOrMaximize)); } public void setResizing(final boolean resizing) { @@ -188,26 +98,204 @@ public class WindowManagerProxy { }); } - public int getDockSide() { + /** Sets a touch region */ + public void setTouchRegion(Rect region) { + synchronized (mDockedRect) { + mTouchableRegion.set(region); + } + mExecutor.execute(mSetTouchableRegionRunnable); + } + + static void applyResizeSplits(int position, SplitDisplayLayout splitLayout) { + WindowContainerTransaction t = new WindowContainerTransaction(); + splitLayout.resizeSplits(position, t); + try { + ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(t, + null /* organizer */); + } catch (RemoteException e) { + } + } + + private static boolean getHomeAndRecentsTasks(List<IWindowContainer> out, + IWindowContainer parent) { + boolean resizable = false; try { - return WindowManagerGlobal.getWindowManagerService().getDockedStackSide(); + List<ActivityManager.RunningTaskInfo> rootTasks = parent == null + ? ActivityTaskManager.getTaskOrganizerController().getRootTasks( + Display.DEFAULT_DISPLAY, HOME_AND_RECENTS) + : ActivityTaskManager.getTaskOrganizerController().getChildTasks(parent, + HOME_AND_RECENTS); + for (int i = 0, n = rootTasks.size(); i < n; ++i) { + final ActivityManager.RunningTaskInfo ti = rootTasks.get(i); + out.add(ti.token); + if (ti.topActivityType == ACTIVITY_TYPE_HOME) { + resizable = ti.isResizable(); + } + } } catch (RemoteException e) { - Log.w(TAG, "Failed to get dock side: " + e); } - return DOCKED_INVALID; + return resizable; } - public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) { - mDimLayerVisible = visible; - mDimLayerTargetWindowingMode = targetWindowingMode; - mDimLayerAlpha = alpha; - mExecutor.execute(mDimLayerRunnable); + static void applyHomeTasksMinimized(SplitDisplayLayout layout, IWindowContainer parent) { + applyHomeTasksMinimized(layout, parent, null /* transaction */); } - public void setTouchRegion(Rect region) { - synchronized (mDockedRect) { - mTouchableRegion.set(region); + /** + * Assign a fixed override-bounds to home tasks that reflect their geometry while the primary + * split is minimized. This actually "sticks out" of the secondary split area, but when in + * minimized mode, the secondary split gets a 'negative' crop to expose it. + */ + static boolean applyHomeTasksMinimized(SplitDisplayLayout layout, IWindowContainer parent, + WindowContainerTransaction t) { + // Resize the home/recents stacks to the larger minimized-state size + final Rect homeBounds; + final ArrayList<IWindowContainer> homeStacks = new ArrayList<>(); + boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent); + if (isHomeResizable) { + homeBounds = layout.calcMinimizedHomeStackBounds(); + } else { + homeBounds = new Rect(0, 0, layout.mDisplayLayout.width(), + layout.mDisplayLayout.height()); + } + WindowContainerTransaction wct = t != null ? t : new WindowContainerTransaction(); + for (int i = homeStacks.size() - 1; i >= 0; --i) { + wct.setBounds(homeStacks.get(i), homeBounds); + } + if (t != null) { + return isHomeResizable; + } + try { + ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct, + null /* organizer */); + } catch (RemoteException e) { + Log.e(TAG, "Failed to resize home stacks ", e); + } + return isHomeResizable; + } + + /** + * Finishes entering split-screen by reparenting all FULLSCREEN tasks into the secondary split. + * This assumes there is already something in the primary split since that is usually what + * triggers a call to this. In the same transaction, this overrides the home task bounds via + * {@link #applyHomeTasksMinimized}. + * + * @return whether the home stack is resizable + */ + static boolean applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout) { + try { + // Set launchtile first so that any stack created after + // getAllStackInfos and before reparent (even if unlikely) are placed + // correctly. + ActivityTaskManager.getTaskOrganizerController().setLaunchRoot( + DEFAULT_DISPLAY, tiles.mSecondary.token); + List<ActivityManager.RunningTaskInfo> rootTasks = + ActivityTaskManager.getTaskOrganizerController().getRootTasks(DEFAULT_DISPLAY, + null /* activityTypes */); + WindowContainerTransaction wct = new WindowContainerTransaction(); + if (rootTasks.isEmpty()) { + return false; + } + for (int i = rootTasks.size() - 1; i >= 0; --i) { + if (rootTasks.get(i).configuration.windowConfiguration.getWindowingMode() + != WINDOWING_MODE_FULLSCREEN) { + continue; + } + wct.reparent(rootTasks.get(i).token, tiles.mSecondary.token, + true /* onTop */); + } + boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct); + ActivityTaskManager.getTaskOrganizerController() + .applyContainerTransaction(wct, null /* organizer */); + return isHomeResizable; + } catch (RemoteException e) { + Log.w(TAG, "Error moving fullscreen tasks to secondary split: " + e); + } + return false; + } + + private static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) { + final int atype = ti.configuration.windowConfiguration.getActivityType(); + return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS; + } + + /** + * Reparents all tile members back to their display and resets home task override bounds. + * @param dismissOrMaximize When {@code true} this resolves the split by closing the primary + * 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) { + try { + // Set launch root first so that any task created after getChildContainers and + // before reparent (pretty unlikely) are put into fullscreen. + ActivityTaskManager.getTaskOrganizerController().setLaunchRoot(Display.DEFAULT_DISPLAY, + null); + // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished + // plus specific APIs to clean this up. + List<ActivityManager.RunningTaskInfo> primaryChildren = + ActivityTaskManager.getTaskOrganizerController().getChildTasks( + tiles.mPrimary.token, null /* activityTypes */); + List<ActivityManager.RunningTaskInfo> secondaryChildren = + ActivityTaskManager.getTaskOrganizerController().getChildTasks( + tiles.mSecondary.token, null /* activityTypes */); + // In some cases (eg. non-resizable is launched), system-server will leave split-screen. + // as a result, the above will not capture any tasks; yet, we need to clean-up the + // home task bounds. + List<ActivityManager.RunningTaskInfo> freeHomeAndRecents = + ActivityTaskManager.getTaskOrganizerController().getRootTasks( + Display.DEFAULT_DISPLAY, HOME_AND_RECENTS); + if (primaryChildren.isEmpty() && secondaryChildren.isEmpty() + && freeHomeAndRecents.isEmpty()) { + return; + } + WindowContainerTransaction wct = new WindowContainerTransaction(); + if (dismissOrMaximize) { + // Dismissing, so move all primary split tasks first + for (int i = primaryChildren.size() - 1; i >= 0; --i) { + wct.reparent(primaryChildren.get(i).token, null /* parent */, + true /* onTop */); + } + // 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) { + final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i); + wct.reparent(ti.token, null /* parent */, true /* onTop */); + if (isHomeOrRecentTask(ti)) { + wct.setBounds(ti.token, null); + } + } + } else { + // Maximize, so move non-home secondary split first + for (int i = secondaryChildren.size() - 1; i >= 0; --i) { + if (isHomeOrRecentTask(secondaryChildren.get(i))) { + continue; + } + wct.reparent(secondaryChildren.get(i).token, null /* parent */, + true /* onTop */); + } + // Find and place home tasks in-between. This simulates the fact that there was + // nothing behind the primary split's tasks. + for (int i = secondaryChildren.size() - 1; i >= 0; --i) { + final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i); + if (isHomeOrRecentTask(ti)) { + wct.reparent(ti.token, null /* parent */, true /* onTop */); + // reset bounds too + wct.setBounds(ti.token, null); + } + } + for (int i = primaryChildren.size() - 1; i >= 0; --i) { + wct.reparent(primaryChildren.get(i).token, null /* parent */, + true /* onTop */); + } + } + for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) { + wct.setBounds(freeHomeAndRecents.get(i).token, null); + } + ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct, + null /* organizer */); + } catch (RemoteException e) { + Log.w(TAG, "Failed to remove stack: " + e); } - mExecutor.execute(mSetTouchableRegionRunnable); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java index 93f58053f486..55a20fae4ffd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java @@ -51,10 +51,10 @@ import android.util.Pair; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.Dependency; -import com.android.systemui.DockedStackExistsListener; import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.dagger.qualifiers.UiBackground; +import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.NotificationChannels; @@ -80,11 +80,13 @@ public class InstantAppNotifier extends SystemUI private final CommandQueue mCommandQueue; private boolean mDockedStackExists; private KeyguardStateController mKeyguardStateController; + private final Divider mDivider; @Inject public InstantAppNotifier(Context context, CommandQueue commandQueue, - @UiBackground Executor uiBgExecutor) { + @UiBackground Executor uiBgExecutor, Divider divider) { super(context); + mDivider = divider; mCommandQueue = commandQueue; mUiBgExecutor = uiBgExecutor; } @@ -103,7 +105,7 @@ public class InstantAppNotifier extends SystemUI mCommandQueue.addCallback(this); mKeyguardStateController.addCallback(this); - DockedStackExistsListener.register( + mDivider.registerInSplitScreenListener( exists -> { mDockedStackExists = exists; updateForegroundInstantApps(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index ba9ba6c9c702..18777bce8679 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -63,7 +63,6 @@ import android.widget.FrameLayout; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; -import com.android.systemui.DockedStackExistsListener; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.assist.AssistHandleViewController; @@ -75,6 +74,7 @@ import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.WindowManagerWrapper; +import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.policy.DeadZone; @@ -862,7 +862,8 @@ public class NavigationBarView extends FrameLayout implements getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); - DockedStackExistsListener.register(mDockedListener); + Divider divider = Dependency.get(Divider.class); + divider.registerInSplitScreenListener(mDockedListener); updateOrientationViews(); reloadNavIcons(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 823adff6cb91..419d1364f109 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -163,7 +163,6 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.stackdivider.Divider; -import com.android.systemui.stackdivider.WindowManagerProxy; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; @@ -1409,8 +1408,11 @@ public class StatusBar extends SystemUI implements DemoMode, if (!mRecentsOptional.isPresent()) { return false; } - int dockSide = WindowManagerProxy.getInstance().getDockSide(); - if (dockSide == WindowManager.DOCKED_INVALID) { + Divider divider = null; + if (mDividerOptional.isPresent()) { + divider = mDividerOptional.get(); + } + if (divider == null || !divider.inSplitMode()) { final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(mDisplayId); if (navbarPos == NAV_BAR_POS_INVALID) { return false; @@ -1420,16 +1422,13 @@ public class StatusBar extends SystemUI implements DemoMode, : SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; return mRecentsOptional.get().splitPrimaryTask(createMode, null, metricsDockAction); } else { - if (mDividerOptional.isPresent()) { - Divider divider = mDividerOptional.get(); - if (divider.isMinimized() && !divider.isHomeStackResizable()) { - // Undocking from the minimized state is not supported - return false; - } else { - divider.onUndockingTask(); - if (metricsUndockAction != -1) { - mMetricsLogger.action(metricsUndockAction); - } + if (divider.isMinimized() && !divider.isHomeStackResizable()) { + // Undocking from the minimized state is not supported + return false; + } else { + divider.onUndockingTask(); + if (metricsUndockAction != -1) { + mMetricsLogger.action(metricsUndockAction); } } } diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index 7dad05df8f2c..9227cf0395d3 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -48,8 +48,8 @@ import javax.inject.Singleton; public class DisplayImeController implements DisplayController.OnDisplaysChangedListener { private static final String TAG = "DisplayImeController"; - static final int ANIMATION_DURATION_SHOW_MS = 275; - static final int ANIMATION_DURATION_HIDE_MS = 340; + public static final int ANIMATION_DURATION_SHOW_MS = 275; + public static final int ANIMATION_DURATION_HIDE_MS = 340; static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); private static final int DIRECTION_NONE = 0; private static final int DIRECTION_SHOW = 1; diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java index 64b0b66a47a9..4652abfa0721 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java @@ -35,6 +35,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.SystemProperties; import android.provider.Settings; +import android.util.DisplayMetrics; import android.util.RotationUtils; import android.util.Size; import android.view.Display; @@ -191,6 +192,11 @@ public class DisplayLayout { return mDensityDpi; } + /** Get the density scale for the display. */ + public float density() { + return mDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; + } + /** Get whether this layout is landscape. */ public boolean isLandscape() { return mWidth > mHeight; diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index ab8e975098a2..3c39b39c4a9d 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -111,6 +111,7 @@ import static com.android.server.wm.TaskProto.ADJUST_DIVIDER_AMOUNT; import static com.android.server.wm.TaskProto.ADJUST_IME_AMOUNT; import static com.android.server.wm.TaskProto.ANIMATING_BOUNDS; import static com.android.server.wm.TaskProto.BOUNDS; +import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER; import static com.android.server.wm.TaskProto.DEFER_REMOVAL; import static com.android.server.wm.TaskProto.DISPLAYED_BOUNDS; import static com.android.server.wm.TaskProto.DISPLAY_ID; @@ -731,53 +732,14 @@ class ActivityStack extends Task implements BoundsAnimationTarget { newBounds); hasNewOverrideBounds = true; } - - // Use override windowing mode to prevent extra bounds changes if inheriting the mode. - if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { - // If entering split screen or if something about the available split area changes, - // recalculate the split windows to match the new configuration. - if (rotationChanged || windowingModeChanged - || prevDensity != getConfiguration().densityDpi - || prevScreenW != getConfiguration().screenWidthDp - || prevScreenH != getConfiguration().screenHeightDp) { - calculateDockedBoundsForConfigChange(newParentConfig, newBounds); - hasNewOverrideBounds = true; - } - } } if (windowingModeChanged) { - // Use override windowing mode to prevent extra bounds changes if inheriting the mode. - if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - getStackDockedModeBounds(null /* dockedBounds */, null /* currentTempTaskBounds */, - newBounds /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */); - // immediately resize so docked bounds are available in onSplitScreenModeActivated - setTaskDisplayedBounds(null); - setTaskBounds(newBounds); - setBounds(newBounds); - newBounds.set(newBounds); - } else if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { - Rect dockedBounds = display.getRootSplitScreenPrimaryTask().getBounds(); - final boolean isMinimizedDock = - display.mDisplayContent.getDockedDividerController().isMinimizedDock(); - if (isMinimizedDock) { - Task topTask = display.getRootSplitScreenPrimaryTask().getTopMostTask(); - if (topTask != null) { - dockedBounds = topTask.getBounds(); - } - } - getStackDockedModeBounds(dockedBounds, null /* currentTempTaskBounds */, - newBounds /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */); - hasNewOverrideBounds = true; - } + display.onStackWindowingModeChanged(this); } if (hasNewOverrideBounds) { - if (inSplitScreenPrimaryWindowingMode()) { - mStackSupervisor.resizeDockedStackLocked(new Rect(newBounds), - null /* tempTaskBounds */, null /* tempTaskInsetBounds */, - null /* tempOtherTaskBounds */, null /* tempOtherTaskInsetBounds */, - PRESERVE_WINDOWS, true /* deferResume */); + if (inSplitScreenWindowingMode()) { + setBounds(newBounds); } else if (overrideWindowingMode != WINDOWING_MODE_PINNED) { // For pinned stack, resize is now part of the {@link WindowContainerTransaction} resize(new Rect(newBounds), null /* tempTaskBounds */, @@ -920,11 +882,7 @@ class ActivityStack extends Task implements BoundsAnimationTarget { // warning toast about it. mAtmService.getTaskChangeNotificationController() .notifyActivityDismissingDockedStack(); - final ActivityStack primarySplitStack = display.getRootSplitScreenPrimaryTask(); - primarySplitStack.setWindowingModeInSurfaceTransaction(WINDOWING_MODE_UNDEFINED, - false /* animate */, false /* showRecents */, - false /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */, - primarySplitStack == this ? creating : false); + display.onSplitScreenModeDismissed(); } } @@ -1218,7 +1176,7 @@ class ActivityStack extends Task implements BoundsAnimationTarget { display.getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN); if (topFullScreenStack != null) { final ActivityStack primarySplitScreenStack = display.getRootSplitScreenPrimaryTask(); - if (display.getIndexOf(topFullScreenStack) + if (primarySplitScreenStack != null && display.getIndexOf(topFullScreenStack) > display.getIndexOf(primarySplitScreenStack)) { primarySplitScreenStack.moveToFront(reason + " splitScreenToTop"); } @@ -3994,17 +3952,6 @@ class ActivityStack extends Task implements BoundsAnimationTarget { ? ((WindowContainer) oldParent).getDisplayContent() : null; super.onParentChanged(newParent, oldParent); - if (display != null && inSplitScreenPrimaryWindowingMode() - // only do this for the base stack - && !newParent.inSplitScreenPrimaryWindowingMode()) { - // If we created a docked stack we want to resize it so it resizes all other stacks - // in the system. - getStackDockedModeBounds(null /* dockedBounds */, null /* currentTempTaskBounds */, - mTmpRect /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */); - mStackSupervisor.resizeDockedStackLocked(getRequestedOverrideBounds(), mTmpRect, - mTmpRect2, null, null, PRESERVE_WINDOWS); - } - // Resume next focusable stack after reparenting to another display if we aren't removing // the prevous display. if (oldDisplay != null && oldDisplay.isRemoving()) { @@ -4950,6 +4897,12 @@ class ActivityStack extends Task implements BoundsAnimationTarget { } @Override + public SurfaceControl getParentSurfaceControl() { + // Tile is a "virtual" parent, so we need to intercept the parent surface here + return mTile != null ? mTile.getSurfaceControl() : super.getParentSurfaceControl(); + } + + @Override void removeImmediately() { // TODO(task-hierarchy): remove this override when tiles are in hierarchy if (mTile != null) { @@ -5002,6 +4955,10 @@ class ActivityStack extends Task implements BoundsAnimationTarget { if (!matchParentBounds()) { final Rect bounds = getRequestedOverrideBounds(); bounds.dumpDebug(proto, BOUNDS); + } else if (getStack().getTile() != null) { + // use tile's bounds here for cts. + final Rect bounds = getStack().getTile().getRequestedOverrideBounds(); + bounds.dumpDebug(proto, BOUNDS); } getOverrideDisplayedBounds().dumpDebug(proto, DISPLAYED_BOUNDS); mAdjustedBounds.dumpDebug(proto, ADJUSTED_BOUNDS); @@ -5021,6 +4978,8 @@ class ActivityStack extends Task implements BoundsAnimationTarget { proto.write(SURFACE_HEIGHT, mSurfaceControl.getHeight()); } + proto.write(CREATED_BY_ORGANIZER, this instanceof TaskTile); + proto.end(token); } } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index a582f2159dfc..a2e880171458 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -2447,7 +2447,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // split-screen in split-screen. mService.getTaskChangeNotificationController() .notifyActivityDismissingDockedStack(); - moveTasksToFullscreenStackLocked(dockedStack, actualStack == dockedStack); + dockedStack.getDisplay().onSplitScreenModeDismissed(); + dockedStack.getDisplay().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS, + true /* notifyClients */); } return; } @@ -2809,7 +2811,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final DisplayContent display = task.getStack().getDisplay(); final ActivityStack topSecondaryStack = display.getTopStackInWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); - if (topSecondaryStack.isActivityTypeHome()) { + if (topSecondaryStack != null && topSecondaryStack.isActivityTypeHome()) { // If the home activity is the top split-screen secondary stack, then the // primary split-screen stack is in the minimized mode which means it can't // receive input keys, so we should move the focused app to the home app so that diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index f2917c54c3fa..f777e909510d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -37,7 +37,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -229,6 +228,7 @@ import android.util.proto.ProtoOutputStream; import android.view.IRecentsAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; +import android.view.WindowContainerTransaction; import android.view.WindowManager; import com.android.internal.R; @@ -2269,6 +2269,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { synchronized (mGlobalLock) { final long ident = Binder.clearCallingIdentity(); try { + if (WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) { + return setTaskWindowingModeSplitScreen(taskId, windowingMode, toTop); + } final Task task = mRootWindowContainer.anyTaskForId(taskId, MATCH_TASK_IN_STACKS_ONLY); if (task == null) { @@ -2286,10 +2289,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } final ActivityStack stack = task.getStack(); + // Convert some windowing-mode changes into root-task reparents for split-screen. + if (stack.getTile() != null) { + stack.getDisplay().onSplitScreenModeDismissed(); + } if (toTop) { stack.moveToFront("setTaskWindowingMode", task); } stack.setWindowingMode(windowingMode); + stack.getDisplay().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS, + true /* notifyClients */); return true; } finally { Binder.restoreCallingIdentity(ident); @@ -2719,36 +2728,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { synchronized (mGlobalLock) { final long ident = Binder.clearCallingIdentity(); try { - if (isInLockTaskMode()) { - Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: Is in lock task mode=" - + getLockTaskModeState()); - return false; - } - - final Task task = mRootWindowContainer.anyTaskForId(taskId, - MATCH_TASK_IN_STACKS_ONLY); - if (task == null) { - Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: No task for id=" + taskId); - return false; - } - if (!task.isActivityTypeStandardOrUndefined()) { - throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move" - + " non-standard task " + taskId + " to split-screen windowing mode"); - } - - if (DEBUG_STACK) Slog.d(TAG_STACK, - "setTaskWindowingModeSplitScreenPrimary: moving task=" + taskId - + " to createMode=" + createMode + " toTop=" + toTop); - mWindowManager.setDockedStackCreateStateLocked(createMode, initialBounds); - final int windowingMode = task.getWindowingMode(); - final ActivityStack stack = task.getStack(); - if (toTop) { - stack.moveToFront("setTaskWindowingModeSplitScreenPrimary", task); - } - stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, animate, showRecents, - false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, - false /* creating */); - return windowingMode != task.getWindowingMode(); + return setTaskWindowingModeSplitScreen(taskId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, + toTop); } finally { Binder.restoreCallingIdentity(ident); } @@ -2756,6 +2737,49 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } /** + * Moves the specified task into a split-screen tile. + */ + private boolean setTaskWindowingModeSplitScreen(int taskId, int windowingMode, boolean toTop) { + if (!WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) { + throw new IllegalArgumentException("Calling setTaskWindowingModeSplitScreen with non" + + "split-screen mode: " + windowingMode); + } + if (isInLockTaskMode()) { + Slog.w(TAG, "setTaskWindowingModeSplitScreen: Is in lock task mode=" + + getLockTaskModeState()); + return false; + } + + final Task task = mRootWindowContainer.anyTaskForId(taskId, + MATCH_TASK_IN_STACKS_ONLY); + if (task == null) { + Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: No task for id=" + taskId); + return false; + } + if (!task.isActivityTypeStandardOrUndefined()) { + throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move" + + " non-standard task " + taskId + " to split-screen windowing mode"); + } + + final int prevMode = task.getWindowingMode(); + final ActivityStack stack = task.getStack(); + TaskTile tile = null; + for (int i = stack.getDisplay().getStackCount() - 1; i >= 0; --i) { + tile = stack.getDisplay().getStackAt(i).asTile(); + if (tile != null && tile.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + break; + } + } + if (tile == null) { + throw new IllegalStateException("Can't enter split without associated tile"); + } + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reparent(stack.mRemoteToken, tile.mRemoteToken, toTop); + mTaskOrganizerController.applyContainerTransaction(wct, null); + return prevMode != task.getWindowingMode(); + } + + /** * Removes stacks in the input windowing modes from the system if they are of activity type * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED */ @@ -3963,46 +3987,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } /** - * Dismisses split-screen multi-window mode. - * @param toTop If true the current primary split-screen stack will be placed or left on top. - */ - @Override - public void dismissSplitScreenMode(boolean toTop) { - enforceCallerIsRecentsOrHasPermission( - MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()"); - final long ident = Binder.clearCallingIdentity(); - try { - synchronized (mGlobalLock) { - final ActivityStack stack = - mRootWindowContainer.getDefaultDisplay().getRootSplitScreenPrimaryTask(); - if (stack == null) { - Slog.w(TAG, "dismissSplitScreenMode: primary split-screen stack not found."); - return; - } - - if (toTop) { - // Caller wants the current split-screen primary stack to be the top stack after - // it goes fullscreen, so move it to the front. - stack.moveToFront("dismissSplitScreenMode"); - } else { - // In this case the current split-screen primary stack shouldn't be the top - // stack after it goes fullscreen, so we move the focus to the top-most - // split-screen secondary stack next to it. - final ActivityStack otherStack = stack.getDisplay().getTopStackInWindowingMode( - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); - if (otherStack != null) { - otherStack.moveToFront("dismissSplitScreenMode_other"); - } - } - - stack.setWindowingMode(WINDOWING_MODE_UNDEFINED); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - /** * Dismisses Pip * @param animate True if the dismissal should be animated. * @param animationDuration The duration of the resize animation in milliseconds or -1 if the diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index b0c6c64558e6..55a41ab3d79a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -31,6 +30,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.app.WindowConfiguration.isSplitScreenWindowingMode; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -55,9 +55,6 @@ import static android.view.Surface.ROTATION_90; import static android.view.View.GONE; import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; -import static android.view.WindowManager.DOCKED_BOTTOM; -import static android.view.WindowManager.DOCKED_INVALID; -import static android.view.WindowManager.DOCKED_TOP; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; @@ -2626,54 +2623,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo void adjustForImeIfNeeded() { final WindowState imeWin = mInputMethodWindow; - final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw() - && !mDividerControllerLocked.isImeHideRequested(); - final ActivityStack dockedStack = getRootSplitScreenPrimaryTask(); - final boolean dockVisible = dockedStack != null; - final Task topDockedTask = dockVisible ? dockedStack.getTask((t) -> true): null; - final ActivityStack imeTargetStack = mWmService.getImeFocusStackLocked(); - final int imeDockSide = (dockVisible && imeTargetStack != null) ? - imeTargetStack.getDockSide() : DOCKED_INVALID; - final boolean imeOnTop = (imeDockSide == DOCKED_TOP); - final boolean imeOnBottom = (imeDockSide == DOCKED_BOTTOM); + final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() + && imeWin.isDisplayedLw(); final int imeHeight = mDisplayFrames.getInputMethodWindowVisibleHeight(); - final boolean imeHeightChanged = imeVisible && - imeHeight != mDividerControllerLocked.getImeHeightAdjustedFor(); - - // This includes a case where the docked stack is unminimizing and IME is visible for the - // bottom side stack. The condition prevents adjusting the override task bounds for IME to - // the minimized docked stack bounds. - final boolean dockMinimized = mDividerControllerLocked.isMinimizedDock() - || (topDockedTask != null && imeOnBottom && !dockedStack.isAdjustedForIme() - && dockedStack.getBounds().height() < topDockedTask.getBounds().height()); - - // The divider could be adjusted for IME position, or be thinner than usual, - // or both. There are three possible cases: - // - If IME is visible, and focus is on top, divider is not moved for IME but thinner. - // - If IME is visible, and focus is on bottom, divider is moved for IME and thinner. - // - If IME is not visible, divider is not moved and is normal width. - - if (imeVisible && dockVisible && (imeOnTop || imeOnBottom) && !dockMinimized) { - for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = mTaskContainers.getChildAt(i); - final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM; - if (stack.isVisible() && (imeOnBottom || isDockedOnBottom) - && stack.inSplitScreenWindowingMode()) { - stack.setAdjustedForIme(imeWin, imeOnBottom && imeHeightChanged); - } else { - stack.resetAdjustedForIme(false); - } - } - mDividerControllerLocked.setAdjustedForIme( - imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight); - } else { - for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = mTaskContainers.getChildAt(i); - stack.resetAdjustedForIme(!dockVisible); - } - mDividerControllerLocked.setAdjustedForIme( - false /*ime*/, false /*divider*/, dockVisible /*animate*/, imeWin, imeHeight); - } mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight); } @@ -3453,8 +3405,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mInputMethodTarget = target; mInputMethodTargetWaitingAnim = targetWaitingAnim; assignWindowLayers(false /* setLayoutNeeded */); - mInputMethodControlTarget = computeImeControlTarget(); - mInsetsStateController.onImeControlTargetChanged(mInputMethodControlTarget); updateImeParent(); } @@ -3465,7 +3415,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * * @see #getImeControlTarget() */ - void updateImeControlTarget(WindowState target) { + void updateImeControlTarget(InsetsControlTarget target) { mInputMethodControlTarget = target; mInsetsStateController.onImeControlTargetChanged(mInputMethodControlTarget); } @@ -3503,13 +3453,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * Computes which control-target the IME should be attached to. */ @VisibleForTesting - InsetsControlTarget computeImeControlTarget() { + InsetsControlTarget computeImeControlTarget(InsetsControlTarget appTarget) { if (!isImeAttachedToApp() && mRemoteInsetsControlTarget != null) { return mRemoteInsetsControlTarget; } // Otherwise, we just use the ime target - return mInputMethodTarget; + return appTarget; } void setLayoutNeeded() { @@ -3897,7 +3847,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - /** @returns the orientation of the display when it's rotation is ROTATION_0. */ + /** @return the orientation of the display when it's rotation is ROTATION_0. */ int getNaturalOrientation() { return mBaseDisplayWidth < mBaseDisplayHeight ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; @@ -4338,8 +4288,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } else { mRootSplitScreenPrimaryTask = stack; - mDisplayContent.onSplitScreenModeActivated(); - mDividerControllerLocked.notifyDockedStackExistsChanged(true); } } } @@ -4351,11 +4299,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mRootPinnedTask = null; } else if (stack == mRootSplitScreenPrimaryTask) { mRootSplitScreenPrimaryTask = null; - mDisplayContent.onSplitScreenModeDismissed(); - // Re-set the split-screen create mode whenever the split-screen stack is removed. - mWmService.setDockedStackCreateStateLocked( - SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */); - mDividerControllerLocked.notifyDockedStackExistsChanged(false); } } @@ -5771,6 +5714,33 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent); } + /** @return the tile to create the next stack in. */ + private TaskTile updateLaunchTile(int windowingMode) { + if (!isSplitScreenWindowingMode(windowingMode)) { + // Only split-screen windowing modes interact with tiles. + return null; + } + for (int i = getStackCount() - 1; i >= 0; --i) { + final TaskTile t = getStackAt(i).asTile(); + if (t == null || t.getRequestedOverrideWindowingMode() != windowingMode) { + continue; + } + // If not already set, pick a launch tile which is not the one we are launching + // into. + if (mLaunchTile == null) { + for (int j = 0, n = getStackCount(); j < n; ++j) { + TaskTile tt = getStackAt(j).asTile(); + if (tt != t) { + mLaunchTile = tt; + break; + } + } + } + return t; + } + return mLaunchTile; + } + @VisibleForTesting ActivityStack createStackUnchecked(int windowingMode, int activityType, int stackId, boolean onTop, ActivityInfo info, Intent intent) { @@ -5783,13 +5753,20 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo info.applicationInfo = new ApplicationInfo(); } + TaskTile tile = updateLaunchTile(windowingMode); + if (tile != null) { + // Since this stack will be put into a tile, its windowingMode will be inherited. + windowingMode = WINDOWING_MODE_UNDEFINED; + } final ActivityStack stack = new ActivityStack(this, stackId, mRootWindowContainer.mStackSupervisor, activityType, info, intent); addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM); stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, true /* creating */); - + if (tile != null) { + tile.addChild(stack, 0 /* index */); + } return stack; } @@ -6009,16 +5986,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo void onSplitScreenModeDismissed() { mAtmService.deferWindowLayout(); try { - // Adjust the windowing mode of any stack in secondary split-screen to fullscreen. + mLaunchTile = null; for (int i = getStackCount() - 1; i >= 0; --i) { - final ActivityStack otherStack = getStackAt(i); - if (!otherStack.inSplitScreenSecondaryWindowingMode()) { - continue; + final TaskTile t = getStackAt(i).asTile(); + if (t != null) { + t.removeAllChildren(); } - otherStack.setWindowingMode(WINDOWING_MODE_UNDEFINED, false /* animate */, - false /* showRecents */, false /* enteringSplitScreenMode */, - true /* deferEnsuringVisibility */, false /* creating */); } + mDividerControllerLocked.setMinimizedDockedStack(false /* minimized */, + false /* animate */); } finally { final ActivityStack topFullscreenStack = getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN); @@ -6036,27 +6012,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - void onSplitScreenModeActivated() { - mAtmService.deferWindowLayout(); - try { - // Adjust the windowing mode of any affected by split-screen to split-screen secondary. - final ActivityStack splitScreenPrimaryStack = getRootSplitScreenPrimaryTask(); - for (int i = getStackCount() - 1; i >= 0; --i) { - final ActivityStack otherStack = getStackAt(i); - if (otherStack == splitScreenPrimaryStack - || !otherStack.affectedBySplitScreenResize()) { - continue; - } - otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, - false /* animate */, false /* showRecents */, - true /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */, - false /* creating */); - } - } finally { - mAtmService.continueWindowLayout(); - } - } - /** * Returns true if the {@param windowingMode} is supported based on other parameters passed in. * @param windowingMode The windowing mode we are checking support for. diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index e71371a477f7..539853bcac77 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -525,9 +525,15 @@ public class DisplayRotation { } mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout); mIsWaitingForRemoteRotation = false; - mDisplayContent.sendNewConfiguration(); - if (t != null) { - mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null); + mService.mAtmService.deferWindowLayout(); + try { + mDisplayContent.sendNewConfiguration(); + if (t != null) { + mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, + null /* organizer */); + } + } finally { + mService.mAtmService.continueWindowLayout(); } } } diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index 872379efa389..6431e117c4e6 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -745,7 +745,7 @@ public class DockedStackDividerController { * @param minimizedDock Whether the docked stack is currently minimized. * @param animate Whether to animate the change. */ - private void setMinimizedDockedStack(boolean minimizedDock, boolean animate) { + void setMinimizedDockedStack(boolean minimizedDock, boolean animate) { final boolean wasMinimized = mMinimizedDock; mMinimizedDock = minimizedDock; if (minimizedDock == wasMinimized) { diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 00947d766e89..44034edaa4bf 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -31,14 +31,14 @@ import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER; +import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.KeyguardControllerProto.AOD_SHOWING; import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_OCCLUDED_STATES; import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_SHOWING; import static com.android.server.wm.KeyguardOccludedProto.DISPLAY_ID; import static com.android.server.wm.KeyguardOccludedProto.KEYGUARD_OCCLUDED; -import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import android.os.IBinder; import android.os.RemoteException; @@ -411,8 +411,7 @@ class KeyguardController { if (stack == null) { return; } - mStackSupervisor.moveTasksToFullscreenStackLocked(stack, - stack.isFocusedStackOnDisplay()); + mRootWindowContainer.getDefaultDisplay().onSplitScreenModeDismissed(); } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 944e0ae3d73f..be68c5dc3796 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; -import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; @@ -414,12 +413,7 @@ public class RecentsAnimationController implements DeathRecipient { } // Save the minimized home height - final ActivityStack dockedStack = - mDisplayContent.getRootSplitScreenPrimaryTask(); - mDisplayContent.getDockedDividerController().getHomeStackBoundsInDockedMode( - mDisplayContent.getConfiguration(), - dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide(), - mMinimizedHomeBounds); + mMinimizedHomeBounds = mDisplayContent.getRootHomeTask().getBounds(); mService.mWindowPlacerLocked.performSurfacePlacement(); @@ -842,8 +836,8 @@ public class RecentsAnimationController implements DeathRecipient { mTask = task; mIsRecentTaskInvisible = isRecentTaskInvisible; final WindowContainer container = mTask.getParent(); - container.getRelativeDisplayedPosition(mPosition); mBounds.set(container.getDisplayedBounds()); + mPosition.set(mBounds.left, mBounds.top); } RemoteAnimationTarget createRemoteAnimationTarget() { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 0aed691c36a6..9279271fb976 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2144,12 +2144,6 @@ class Task extends WindowContainer<WindowContainer> { // For floating tasks, calculate the smallest width from the bounds of the task inOutConfig.smallestScreenWidthDp = (int) ( Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density); - } else if (WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) { - // Iterating across all screen orientations, and return the minimum of the task - // width taking into account that the bounds might change because the snap - // algorithm snaps to a different value - inOutConfig.smallestScreenWidthDp = - getSmallestScreenWidthDpForDockedBounds(mTmpFullBounds); } // otherwise, it will just inherit } @@ -3248,6 +3242,10 @@ class Task extends WindowContainer<WindowContainer> { return this; } + TaskTile asTile() { + return null; + } + // TODO(task-merge): Figure-out how this should work with hierarchy tasks. boolean shouldBeVisible(ActivityRecord starting) { return true; diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 4b13a0c1f75d..4d5621cd5b32 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -492,6 +492,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub if (!(container instanceof Task)) { throw new IllegalArgumentException("Invalid container in hierarchy op"); } + if (container.getDisplayContent() == null) { + Slog.w(TAG, "Container is no longer attached: " + container); + return 0; + } if (hop.isReparent()) { // special case for tiles since they are "virtual" parents if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) { diff --git a/services/core/java/com/android/server/wm/TaskTile.java b/services/core/java/com/android/server/wm/TaskTile.java index 369db05452f7..74d5c338a68d 100644 --- a/services/core/java/com/android/server/wm/TaskTile.java +++ b/services/core/java/com/android/server/wm/TaskTile.java @@ -31,7 +31,6 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.IBinder; import android.util.Slog; -import android.view.SurfaceControl; import java.util.ArrayList; import java.util.Comparator; @@ -78,30 +77,9 @@ public class TaskTile extends ActivityStack { // Virtual parent, so don't notify children. } - /** - * If there is a disconnection, this will clean up any vestigial surfaces left on the tile - * leash by moving known children to a new surfacecontrol and then removing the old one. - */ - void cleanupSurfaces() { - if (mSurfaceControl == null) { - return; - } - SurfaceControl oldSurface = mSurfaceControl; - WindowContainer parentWin = getParent(); - if (parentWin == null) { - return; - } - mSurfaceControl = parentWin.makeChildSurface(null).setName("TaskTile " + mTaskId + " - " - + getRequestedOverrideWindowingMode()).setContainerLayer().build(); - SurfaceControl.Transaction t = parentWin.getPendingTransaction(); - t.show(mSurfaceControl); - for (int i = 0; i < mChildren.size(); ++i) { - if (mChildren.get(i).getSurfaceControl() == null) { - continue; - } - mChildren.get(i).reparentSurfaceControl(t, mSurfaceControl); - } - t.remove(oldSurface); + @Override + TaskTile asTile() { + return this; } @Override @@ -215,6 +193,12 @@ public class TaskTile extends ActivityStack { super.removeImmediately(); } + @Override + void taskOrganizerDied() { + super.taskOrganizerDied(); + removeImmediately(); + } + static TaskTile forToken(IBinder token) { try { return (TaskTile) ((TaskToken) token).getContainer(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 2bb67035f44b..1b0d177cab87 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7320,7 +7320,9 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mGlobalLock) { final WindowState imeTarget = mWindowMap.get(imeTargetWindowToken); if (imeTarget != null) { - imeTarget.getDisplayContent().updateImeControlTarget(imeTarget); + InsetsControlTarget controlTarget = + imeTarget.getDisplayContent().computeImeControlTarget(imeTarget); + imeTarget.getDisplayContent().updateImeControlTarget(controlTarget); } } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 46081c5dde92..ed4e6842906d 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2662,18 +2662,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWmService.mTaskSnapshotController.onAppDied(win.mActivityRecord); } win.removeIfPossible(shouldKeepVisibleDeadAppWindow()); - if (win.mAttrs.type == TYPE_DOCK_DIVIDER) { - // The owner of the docked divider died :( We reset the docked stack, - // just in case they have the divider at an unstable position. Better - // also reset drag resizing state, because the owner can't do it - // anymore. - final ActivityStack stack = - dc.getRootSplitScreenPrimaryTask(); - if (stack != null) { - stack.resetDockedStackToMiddle(); - } - resetSplitScreenResizing = true; - } } else if (mHasSurface) { Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid."); WindowState.this.removeIfPossible(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index fa182d68d917..d46975cc6552 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -29,6 +29,7 @@ import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_SWITCHES_CANCELED; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; @@ -64,8 +65,10 @@ import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.IApplicationThread; +import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -80,6 +83,9 @@ import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.service.voice.IVoiceInteractionSession; import android.view.Gravity; +import android.view.ITaskOrganizer; +import android.view.IWindowContainer; +import android.view.SurfaceControl; import androidx.test.filters.SmallTest; @@ -999,7 +1005,8 @@ public class ActivityStarterTests extends ActivityTestsBase { assertThat(outActivity[0].inSplitScreenWindowingMode()).isFalse(); // Move activity to split-screen-primary stack and make sure it has the focus. - top.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + TestSplitOrganizer splitOrg = new TestSplitOrganizer(mService, top.getDisplayId()); + splitOrg.mPrimary.addChild(top.getRootTask(), 0 /* index */); top.getRootTask().moveToFront("testWindowingModeOptionsLaunchAdjacent"); // Activity must landed on split-screen-secondary when launch adjacent. @@ -1022,4 +1029,58 @@ public class ActivityStarterTests extends ActivityTestsBase { verify(recentTasks, times(1)).add(any()); } + + static class TestSplitOrganizer extends ITaskOrganizer.Stub { + final ActivityTaskManagerService mService; + TaskTile mPrimary; + TaskTile mSecondary; + boolean mInSplit = false; + int mDisplayId; + TestSplitOrganizer(ActivityTaskManagerService service, int displayId) { + mService = service; + mDisplayId = displayId; + mService.mTaskOrganizerController.registerTaskOrganizer(this, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + mService.mTaskOrganizerController.registerTaskOrganizer(this, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + IWindowContainer primary = mService.mTaskOrganizerController.createRootTask( + displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token; + mPrimary = TaskTile.forToken(primary.asBinder()); + IWindowContainer secondary = mService.mTaskOrganizerController.createRootTask( + displayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token; + mSecondary = TaskTile.forToken(secondary.asBinder()); + } + @Override + public void taskAppeared(ActivityManager.RunningTaskInfo info) { + } + @Override + public void taskVanished(IWindowContainer wc) { + } + @Override + public void transactionReady(int id, SurfaceControl.Transaction t) { + } + @Override + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { + if (mInSplit) { + return; + } + if (info.topActivityType != ACTIVITY_TYPE_UNDEFINED) { + if (info.configuration.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + mInSplit = true; + mService.mTaskOrganizerController.setLaunchRoot(mDisplayId, + mSecondary.mRemoteToken); + // move everything to secondary because test expects this but usually sysui + // does it. + DisplayContent dc = mService.mRootWindowContainer.getDisplayContent(mDisplayId); + for (int i = dc.getStackCount() - 1; i >= 0; --i) { + if (!WindowConfiguration.isSplitScreenWindowingMode( + dc.getStackAt(i).getWindowingMode())) { + mSecondary.addChild(dc.getStackAt(i), 0); + } + } + } + } + } + }; } diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index a672a95dcc12..444906977f47 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -1098,7 +1098,6 @@ public class RecentTasksTest extends ActivityTestsBase { assertSecurityException(expectCallable, () -> mService.setTaskWindowingModeSplitScreenPrimary(0, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect(), true)); - assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true)); assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0)); assertSecurityException(expectCallable, () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect())); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index 0312df6d34fc..cd53eced948f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -28,19 +28,17 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECOND import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; - import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -49,6 +47,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.StackInfo; import android.app.PictureInPictureParams; +import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; @@ -56,7 +55,6 @@ import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; -import android.content.pm.ActivityInfo; import android.util.Rational; import android.view.Display; import android.view.ITaskOrganizer; @@ -458,9 +456,9 @@ public class TaskOrganizerTests extends WindowTestsBase { private List<TaskTile> getTaskTiles(DisplayContent dc) { ArrayList<TaskTile> out = new ArrayList<>(); for (int i = dc.getStackCount() - 1; i >= 0; --i) { - final Task t = dc.getStackAt(i); - if (t instanceof TaskTile) { - out.add((TaskTile) t); + final TaskTile t = dc.getStackAt(i).asTile(); + if (t != null) { + out.add(t); } } return out; |