Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5bc9594c authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add divider view support for app-pairs"

parents 53bb3cf0 0545edfa
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -207,12 +207,19 @@ public class WindowlessWindowManager implements IWindowSession {
    }

    /** @hide */
    @Nullable
    protected SurfaceControl getSurfaceControl(View rootView) {
        final ViewRootImpl root = rootView.getViewRootImpl();
        if (root == null) {
            return null;
        }
        final State s = mStateForWindow.get(root.mWindow.asBinder());
        return getSurfaceControl(root.mWindow);
    }

    /** @hide */
    @Nullable
    protected SurfaceControl getSurfaceControl(IWindow window) {
        final State s = mStateForWindow.get(window.asBinder());
        if (s == null) {
            return null;
        }
+27 −0
Original line number Diff line number Diff line
<!--
  ~ 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.
  -->

<com.android.wm.shell.apppairs.DividerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <View
        style="@style/DockedDividerBackground"
        android:id="@+id/docked_divider_background"
        android:background="@color/docked_divider_background"/>

</com.android.wm.shell.apppairs.DividerView>
+45 −10
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import androidx.annotation.NonNull;

import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;

import java.io.PrintWriter;
@@ -41,8 +42,6 @@ import java.io.PrintWriter;
 * {@link #mTaskInfo1} and {@link #mTaskInfo2} in the pair.
 * Also includes all UI for managing the pair like the divider.
 */
// TODO: Add divider
// TODO: Handle display rotation
class AppPair implements ShellTaskOrganizer.TaskListener {
    private static final String TAG = AppPair.class.getSimpleName();

@@ -55,10 +54,13 @@ class AppPair implements ShellTaskOrganizer.TaskListener {

    private final AppPairsController mController;
    private final SyncTransactionQueue mSyncQueue;
    private final DisplayController mDisplayController;
    private AppPairLayout mAppPairLayout;

    AppPair(AppPairsController controller) {
        mController = controller;
        mSyncQueue = controller.getSyncTransactionQueue();
        mDisplayController = controller.getDisplayController();
    }

    int getRootTaskId() {
@@ -90,13 +92,12 @@ class AppPair implements ShellTaskOrganizer.TaskListener {

        mTaskInfo1 = task1;
        mTaskInfo2 = task2;
        mAppPairLayout = new AppPairLayout(
                mDisplayController.getDisplayContext(mRootTaskInfo.displayId),
                mDisplayController.getDisplay(mRootTaskInfo.displayId),
                mRootTaskInfo.configuration,
                mRootTaskLeash);

        // TODO: properly calculate bounds for pairs.
        final Rect rootBounds = mRootTaskInfo.configuration.windowConfiguration.getBounds();
        final Rect bounds1 = new Rect(
                rootBounds.left, rootBounds.top, rootBounds.right / 2, rootBounds.bottom / 2);
        final Rect bounds2 = new Rect(
                bounds1.right, bounds1.bottom, rootBounds.right, rootBounds.bottom);
        final WindowContainerToken token1 = task1.token;
        final WindowContainerToken token2 = task2.token;
        final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -106,8 +107,8 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
                .reparent(token2, mRootTaskInfo.token, true /* onTop */)
                .setWindowingMode(token1, WINDOWING_MODE_MULTI_WINDOW)
                .setWindowingMode(token2, WINDOWING_MODE_MULTI_WINDOW)
                .setBounds(token1, bounds1)
                .setBounds(token2, bounds2)
                .setBounds(token1, mAppPairLayout.getBounds1())
                .setBounds(token2, mAppPairLayout.getBounds2())
                // Moving the root task to top after the child tasks were repareted , or the root
                // task cannot be visible and focused.
                .reorder(mRootTaskInfo.token, true);
@@ -131,6 +132,15 @@ class AppPair implements ShellTaskOrganizer.TaskListener {

        mTaskInfo1 = null;
        mTaskInfo2 = null;
        mAppPairLayout.release();
        mAppPairLayout = null;
    }

    void setVisible(boolean visible) {
        if (mAppPairLayout == null) {
            return;
        }
        mAppPairLayout.setDividerVisibility(visible);
    }

    @Override
@@ -150,13 +160,20 @@ class AppPair implements ShellTaskOrganizer.TaskListener {

        if (mTaskLeash1 == null || mTaskLeash2 == null) return;

        setVisible(true);
        final SurfaceControl dividerLeash = mAppPairLayout.getDividerLeash();
        final Rect dividerBounds = mAppPairLayout.getDividerBounds();

        // TODO: Is there more we need to do here?
        mSyncQueue.runInSync(t -> t
                .setPosition(mTaskLeash1, mTaskInfo1.positionInParent.x,
                        mTaskInfo1.positionInParent.y)
                .setPosition(mTaskLeash2, mTaskInfo2.positionInParent.x,
                        mTaskInfo2.positionInParent.y)
                .setLayer(dividerLeash, Integer.MAX_VALUE)
                .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
                .show(mRootTaskLeash)
                .show(dividerLeash)
                .show(mTaskLeash1)
                .show(mTaskLeash2));
    }
@@ -165,6 +182,24 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
        if (taskInfo.taskId == getRootTaskId()) {
            mRootTaskInfo = taskInfo;

            if (mAppPairLayout != null
                    && mAppPairLayout.updateConfiguration(mRootTaskInfo.configuration)) {
                // Update bounds when there is root bounds or orientation changed.
                final WindowContainerTransaction wct = new WindowContainerTransaction();
                final SurfaceControl dividerLeash = mAppPairLayout.getDividerLeash();
                final Rect dividerBounds = mAppPairLayout.getDividerBounds();
                final Rect bounds1 = mAppPairLayout.getBounds1();
                final Rect bounds2 = mAppPairLayout.getBounds2();

                wct.setBounds(mTaskInfo1.token, bounds1)
                        .setBounds(mTaskInfo2.token, bounds2);
                mController.getTaskOrganizer().applyTransaction(wct);
                mSyncQueue.runInSync(t -> t
                        .setPosition(mTaskLeash1, bounds1.left, bounds1.top)
                        .setPosition(mTaskLeash2, bounds2.left, bounds2.top)
                        .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top));
            }
        } else if (taskInfo.taskId == getTaskId1()) {
            mTaskInfo1 = taskInfo;
        } else if (taskInfo.taskId == getTaskId2()) {
+224 −0
Original line number Diff line number Diff line
/*
 * 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.wm.shell.apppairs;

import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
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.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Binder;
import android.os.IBinder;
import android.view.Display;
import android.view.IWindow;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;

import com.android.wm.shell.R;

/**
 * Records and handles layout of a pair of apps.
 */
// TODO(172704238): add tests
final class AppPairLayout {
    private static final String DIVIDER_WINDOW_TITLE = "AppPairDivider";
    private final Context mContext;
    private final AppPairWindowManager mAppPairWindowManager;
    private final SurfaceControlViewHost mViewHost;

    private final int mDividerWindowWidth;
    private final int mDividerWindowInsets;

    private boolean mIsLandscape;
    private Rect mRootBounds;
    private DIVIDE_POLICY mDividePolicy;

    private DividerView mDividerView;
    private SurfaceControl mDividerLeash;

    AppPairLayout(
            Context context,
            Display display,
            Configuration configuration,
            SurfaceControl rootLeash) {
        mContext = context.createConfigurationContext(configuration);
        mIsLandscape = isLandscape(configuration);
        mRootBounds = configuration.windowConfiguration.getBounds();
        mDividerWindowWidth = mContext.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.docked_stack_divider_thickness);
        mDividerWindowInsets = mContext.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.docked_stack_divider_insets);

        mAppPairWindowManager = new AppPairWindowManager(configuration, rootLeash);
        mViewHost = new SurfaceControlViewHost(mContext, display, mAppPairWindowManager);
        mDividePolicy = DIVIDE_POLICY.MIDDLE;
        mDividePolicy.update(mIsLandscape, mRootBounds, mDividerWindowWidth, mDividerWindowInsets);
    }

    boolean updateConfiguration(Configuration configuration) {
        mAppPairWindowManager.setConfiguration(configuration);
        final Rect rootBounds = configuration.windowConfiguration.getBounds();
        final boolean isLandscape = isLandscape(configuration);
        if (mIsLandscape == isLandscape && isIdenticalBounds(mRootBounds, rootBounds)) {
            return false;
        }

        mIsLandscape = isLandscape;
        mRootBounds = rootBounds;
        mDividePolicy.update(mIsLandscape, mRootBounds, mDividerWindowWidth, mDividerWindowInsets);
        mViewHost.relayout(
                mDividePolicy.mDividerBounds.width(),
                mDividePolicy.mDividerBounds.height());
        // TODO(172704238): handle divider bar rotation.
        return true;
    }

    Rect getBounds1() {
        return mDividePolicy.mBounds1;
    }

    Rect getBounds2() {
        return mDividePolicy.mBounds2;
    }

    Rect getDividerBounds() {
        return mDividePolicy.mDividerBounds;
    }

    SurfaceControl getDividerLeash() {
        return mDividerLeash;
    }

    void release() {
        if (mViewHost == null) return;
        mViewHost.release();
    }

    void setDividerVisibility(boolean visible) {
        if (mDividerView == null) {
            initDivider();
        }
        if (visible) {
            mDividerView.show();
        } else {
            mDividerView.hide();
        }
    }

    private void initDivider() {
        final DividerView dividerView = (DividerView) LayoutInflater.from(mContext)
                .inflate(R.layout.split_divider, null);

        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                mDividePolicy.mDividerBounds.width(),
                mDividePolicy.mDividerBounds.height(),
                TYPE_DOCK_DIVIDER,
                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
                        | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
                PixelFormat.TRANSLUCENT);
        lp.token = new Binder();
        lp.setTitle(DIVIDER_WINDOW_TITLE);
        lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;

        mViewHost.setView(dividerView, lp);
        mDividerView = dividerView;
        mDividerLeash = mAppPairWindowManager.getSurfaceControl(mViewHost.getWindowToken());
    }

    private static boolean isLandscape(Configuration configuration) {
        return configuration.orientation == ORIENTATION_LANDSCAPE;
    }

    private static boolean isIdenticalBounds(Rect bounds1, Rect bounds2) {
        return bounds1.left == bounds2.left && bounds1.top == bounds2.top
                && bounds1.right == bounds2.right && bounds1.bottom == bounds2.bottom;
    }

    /**
     * Indicates the policy of placing divider bar and corresponding split-screens.
     */
    // TODO(172704238): add more divide policy and provide snap to resize feature for divider bar.
    enum DIVIDE_POLICY {
        MIDDLE;

        void update(boolean isLandscape, Rect rootBounds, int dividerWindowWidth,
                int dividerWindowInsets) {
            final int dividerOffset = dividerWindowWidth / 2;
            final int boundsOffset = dividerOffset - dividerWindowInsets;

            mDividerBounds = new Rect(rootBounds);
            mBounds1 = new Rect(rootBounds);
            mBounds2 = new Rect(rootBounds);

            switch (this) {
                case MIDDLE:
                default:
                    if (isLandscape) {
                        mDividerBounds.left = rootBounds.width() / 2 - dividerOffset;
                        mDividerBounds.right = rootBounds.width() / 2 + dividerOffset;
                        mBounds1.left = rootBounds.width() / 2 + boundsOffset;
                        mBounds2.right = rootBounds.width() / 2 - boundsOffset;
                    } else {
                        mDividerBounds.top = rootBounds.height() / 2 - dividerOffset;
                        mDividerBounds.bottom = rootBounds.height() / 2 + dividerOffset;
                        mBounds1.bottom = rootBounds.height() / 2 - boundsOffset;
                        mBounds2.top = rootBounds.height() / 2 + boundsOffset;
                    }
            }
        }

        Rect mDividerBounds;
        Rect mBounds1;
        Rect mBounds2;
    }

    /**
     * WindowManger for app pair. Holds view hierarchy for the root task.
     */
    private static final class AppPairWindowManager extends WindowlessWindowManager {
        AppPairWindowManager(Configuration config, SurfaceControl rootSurface) {
            super(config, rootSurface, null /* hostInputToken */);
        }

        @Override
        public void setTouchRegion(IBinder window, Region region) {
            super.setTouchRegion(window, region);
        }

        @Override
        public SurfaceControl getSurfaceControl(IWindow window) {
            return super.getSurfaceControl(window);
        }

        @Override
        public void setConfiguration(Configuration configuration) {
            super.setConfiguration(configuration);
        }
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -36,4 +36,6 @@ public interface AppPairs {
    void dump(@NonNull PrintWriter pw, String prefix);
    /** Called when the shell organizer has been registered. */
    void onOrganizerRegistered();
    /** Called when the visibility of the keyguard changes. */
    void onKeyguardVisibilityChanged(boolean showing);
}
Loading