diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 4b2fc759a0..258d60c208 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -38,6 +38,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Icon; import android.os.Build; @@ -69,6 +70,7 @@ import com.android.launcher3.tracing.nano.TouchInteractionServiceProto; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.OnboardingPrefs; import com.android.launcher3.util.TraceHelper; +import com.android.launcher3.util.WindowBounds; import com.android.quickstep.inputconsumers.AccessibilityInputConsumer; import com.android.quickstep.inputconsumers.AssistantInputConsumer; import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer; @@ -81,6 +83,7 @@ import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.AssistantUtilities; import com.android.quickstep.util.ProtoTracer; +import com.android.quickstep.util.SplitScreenBounds; import com.android.quickstep.views.RecentsView; import com.android.systemui.plugins.OverscrollPlugin; import com.android.systemui.plugins.PluginListener; @@ -118,7 +121,7 @@ class ArgList extends LinkedList { /** * Service connected by system-UI for handling touch interaction. */ -@TargetApi(Build.VERSION_CODES.Q) +@TargetApi(Build.VERSION_CODES.R) public class TouchInteractionService extends Service implements PluginListener, ProtoTraceable { @@ -229,6 +232,11 @@ public class TouchInteractionService extends Service implements PluginListener mDeviceState.setDeferredGestureRegion(region)); } + public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets) { + WindowBounds wb = new WindowBounds(bounds, insets); + MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb)); + } + /** Deprecated methods **/ public void onQuickStep(MotionEvent motionEvent) { } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index 700af97997..bc702c6596 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -126,6 +126,7 @@ import com.android.quickstep.ViewUtils; import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.RecentsOrientedState; +import com.android.quickstep.util.SplitScreenBounds; import com.android.quickstep.util.TransformParams; import com.android.quickstep.util.WindowSizeStrategy; import com.android.systemui.plugins.ResourceProvider; @@ -147,7 +148,8 @@ import java.util.function.Consumer; @TargetApi(Build.VERSION_CODES.P) public abstract class RecentsView extends PagedView implements Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback, - InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener { + InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener, + SplitScreenBounds.OnChangeListener { private static final String TAG = RecentsView.class.getSimpleName(); @@ -510,6 +512,7 @@ public abstract class RecentsView extends PagedView impl SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener( mIPinnedStackAnimationListener); mOrientationState.initListeners(); + SplitScreenBounds.INSTANCE.addOnChangeListener(this); } @Override @@ -523,6 +526,7 @@ public abstract class RecentsView extends PagedView impl RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); mIdp.removeOnChangeListener(this); SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null); + SplitScreenBounds.INSTANCE.removeOnChangeListener(this); mIPinnedStackAnimationListener.setActivity(null); mOrientationState.destroyListeners(); } @@ -2165,6 +2169,13 @@ public abstract class RecentsView extends PagedView impl return null; } + @Override + public void onSecondaryWindowBoundsChanged() { + // Invalidate the task view size + setInsets(mInsets); + requestLayout(); + } + /** * Enables or disables modal state for RecentsView * @param isModalState diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java index fffbb34bdf..e7ff48f52b 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java +++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java @@ -51,6 +51,7 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.PagedOrientationHandler; +import com.android.launcher3.util.WindowBounds; import java.lang.annotation.Retention; import java.util.function.IntConsumer; @@ -361,7 +362,8 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre float fullHeight = dp.heightPx - insets.top - insets.bottom; if (dp.isMultiWindowMode) { - mSizeStrategy.getMultiWindowSize(mContext, dp, outPivot); + WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(mContext); + outPivot.set(bounds.availableSize.x, bounds.availableSize.y); } else { outPivot.set(fullWidth, fullHeight); } diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java b/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java new file mode 100644 index 0000000000..a770e8e2b5 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java @@ -0,0 +1,112 @@ +/* + * 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.quickstep.util; + +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Insets; +import android.graphics.Rect; +import android.os.Build; +import android.view.WindowInsets.Type; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; + +import com.android.launcher3.R; +import com.android.launcher3.util.DefaultDisplay; +import com.android.launcher3.util.WindowBounds; + +import java.util.ArrayList; + +/** + * Utility class to hold the information abound a window bounds for split screen + */ +@TargetApi(Build.VERSION_CODES.R) +public class SplitScreenBounds { + + public static final SplitScreenBounds INSTANCE = new SplitScreenBounds(); + private final ArrayList mListeners = new ArrayList<>(); + + @Nullable + private WindowBounds mBounds; + + private SplitScreenBounds() { } + + @UiThread + public void setSecondaryWindowBounds(@NonNull WindowBounds bounds) { + if (!bounds.equals(mBounds)) { + mBounds = bounds; + for (OnChangeListener listener : mListeners) { + listener.onSecondaryWindowBoundsChanged(); + } + } + } + + public @NonNull WindowBounds getSecondaryWindowBounds(Context context) { + if (mBounds == null) { + mBounds = createDefaultWindowBounds(context); + } + return mBounds; + } + + /** + * Creates window bounds as 50% of device size + */ + private static WindowBounds createDefaultWindowBounds(Context context) { + WindowMetrics wm = context.getSystemService(WindowManager.class).getMaximumWindowMetrics(); + Insets insets = wm.getWindowInsets().getInsets(Type.systemBars()); + + WindowBounds bounds = new WindowBounds(wm.getBounds(), + new Rect(insets.left, insets.top, insets.right, insets.bottom)); + int rotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation; + int halfDividerSize = context.getResources() + .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2; + + if (rotation == ROTATION_0 || rotation == ROTATION_180) { + bounds.bounds.top = bounds.insets.top + bounds.availableSize.y / 2 + halfDividerSize; + bounds.insets.top = 0; + } else { + bounds.bounds.left = bounds.insets.left + bounds.availableSize.x / 2 + halfDividerSize; + bounds.insets.left = 0; + } + return new WindowBounds(bounds.bounds, bounds.insets); + } + + public void addOnChangeListener(OnChangeListener listener) { + mListeners.add(listener); + } + + public void removeOnChangeListener(OnChangeListener listener) { + mListeners.remove(listener); + } + + /** + * Interface to receive window bounds changes + */ + public interface OnChangeListener { + + /** + * Called when window bounds for secondary window changes + */ + void onSecondaryWindowBoundsChanged(); + } +} diff --git a/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java b/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java index 81a19247ed..84d84f2eea 100644 --- a/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java +++ b/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java @@ -20,13 +20,15 @@ import static com.android.quickstep.SysUINavigationMode.getMode; import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; import static com.android.quickstep.util.LayoutUtils.getDefaultSwipeHeight; +import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; -import android.graphics.PointF; import android.graphics.Rect; +import android.os.Build; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; +import com.android.launcher3.util.WindowBounds; import com.android.quickstep.SysUINavigationMode.Mode; /** @@ -34,20 +36,15 @@ import com.android.quickstep.SysUINavigationMode.Mode; * TODO: Merge is with {@link com.android.quickstep.BaseActivityInterface} once we remove the * state dependent members from {@link com.android.quickstep.LauncherActivityInterface} */ +@TargetApi(Build.VERSION_CODES.R) public abstract class WindowSizeStrategy { - private final PointF mTempPoint = new PointF(); public final boolean rotationSupportedByActivity; private WindowSizeStrategy(boolean rotationSupportedByActivity) { this.rotationSupportedByActivity = rotationSupportedByActivity; } - /** - * Sets the expected window size in multi-window mode - */ - public abstract void getMultiWindowSize(Context context, DeviceProfile dp, PointF out); - /** * Calculates the taskView size for the provided device configuration */ @@ -65,9 +62,9 @@ public abstract class WindowSizeStrategy { final boolean showLargeTaskSize = showOverviewActions(context); if (dp.isMultiWindowMode) { - getMultiWindowSize(context, dp, mTempPoint); - taskWidth = mTempPoint.x; - taskHeight = mTempPoint.y; + WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context); + taskWidth = bounds.availableSize.x; + taskHeight = bounds.availableSize.y; paddingHorz = res.getDimension(R.dimen.multi_window_task_card_horz_space); } else { taskWidth = dp.availableWidthPx; @@ -113,22 +110,6 @@ public abstract class WindowSizeStrategy { public static final WindowSizeStrategy LAUNCHER_ACTIVITY_SIZE_STRATEGY = new WindowSizeStrategy(true) { - @Override - public void getMultiWindowSize(Context context, DeviceProfile dp, PointF out) { - DeviceProfile fullDp = dp.getFullScreenProfile(); - // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to - // account for system insets - out.set(fullDp.availableWidthPx, fullDp.availableHeightPx); - float halfDividerSize = context.getResources() - .getDimension(R.dimen.multi_window_task_divider_size) / 2; - - if (fullDp.isLandscape) { - out.x = out.x / 2 - halfDividerSize; - } else { - out.y = out.y / 2 - halfDividerSize; - } - } - @Override float getExtraSpace(Context context, DeviceProfile dp) { if (dp.isVerticalBarLayout()) { @@ -164,10 +145,6 @@ public abstract class WindowSizeStrategy { public static final WindowSizeStrategy FALLBACK_RECENTS_SIZE_STRATEGY = new WindowSizeStrategy(false) { - @Override - public void getMultiWindowSize(Context context, DeviceProfile dp, PointF out) { - out.set(dp.widthPx, dp.heightPx); - } @Override float getExtraSpace(Context context, DeviceProfile dp) { diff --git a/src/com/android/launcher3/util/WindowBounds.java b/src/com/android/launcher3/util/WindowBounds.java new file mode 100644 index 0000000000..3c2fb62c16 --- /dev/null +++ b/src/com/android/launcher3/util/WindowBounds.java @@ -0,0 +1,47 @@ +/* + * 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.launcher3.util; + +import android.graphics.Point; +import android.graphics.Rect; + +import androidx.annotation.Nullable; + +/** + * Utility class to hold information about window position and layout + */ +public class WindowBounds { + + public final Rect bounds; + public final Rect insets; + public final Point availableSize; + + public WindowBounds(Rect bounds, Rect insets) { + this.bounds = bounds; + this.insets = insets; + availableSize = new Point(bounds.width() - insets.left - insets.right, + bounds.height() - insets.top - insets.bottom); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof WindowBounds)) { + return false; + } + WindowBounds other = (WindowBounds) obj; + return other.bounds.equals(bounds) && other.insets.equals(insets); + } +}