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

Commit 85639e6d authored by Jiaming Liu's avatar Jiaming Liu
Browse files

[Divider] Implement Divider Dragging

Handles touch events on the divider and resize the TaskFragments based
on the drag position. Veils are added during dragging to cover the app
content.

Bug: 293654166
Test: atest DividerContainerTest
Change-Id: Iec5ee32e8d17e7f1fc3c040f4736215eecafee04
parent 99467894
Loading
Loading
Loading
Loading
+455 −73

File changed.

Preview size limit exceeded, changes collapsed.

+1 −3
Original line number Diff line number Diff line
@@ -165,7 +165,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
    /**
     * Expands an existing TaskFragment to fill parent.
     * @param wct WindowContainerTransaction in which the task fragment should be resized.
     * @param fragmentToken token of an existing TaskFragment.
     * @param container the {@link TaskFragmentContainer} to be expanded.
     */
    void expandTaskFragment(@NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentContainer container) {
@@ -174,8 +174,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
        clearAdjacentTaskFragments(wct, fragmentToken);
        updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
        updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);

        container.getTaskContainer().updateDivider(wct);
    }

    /**
+46 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 androidx.window.extensions.embedding;

import android.content.res.Configuration;
import android.view.View;

import androidx.annotation.NonNull;

/** Helper functions for {@link SplitAttributes} */
class SplitAttributesHelper {
    /**
     * Returns whether the split layout direction is reversed. Right-to-left and bottom-to-top are
     * considered reversed.
     */
    static boolean isReversedLayout(
            @NonNull SplitAttributes splitAttributes, @NonNull Configuration configuration) {
        switch (splitAttributes.getLayoutDirection()) {
            case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT:
            case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM:
                return false;
            case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT:
            case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP:
                return true;
            case SplitAttributes.LayoutDirection.LOCALE:
                return configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
            default:
                throw new IllegalArgumentException(
                        "Invalid layout direction:" + splitAttributes.getLayoutDirection());
        }
    }
}
+58 −5
Original line number Diff line number Diff line
@@ -110,7 +110,7 @@ import java.util.function.BiConsumer;
 * Main controller class that manages split states and presentation.
 */
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
        ActivityEmbeddingComponent {
        ActivityEmbeddingComponent, DividerPresenter.DragEventCallback {
    static final String TAG = "SplitController";
    static final boolean ENABLE_SHELL_TRANSITIONS =
            SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
@@ -163,6 +163,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    @GuardedBy("mLock")
    final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();

    /** Map from Task id to {@link DividerPresenter} which manages the divider in the Task. */
    @GuardedBy("mLock")
    private final SparseArray<DividerPresenter> mDividerPresenters = new SparseArray<>();

    /** Callback to Jetpack to notify about changes to split states. */
    @GuardedBy("mLock")
    @Nullable
@@ -195,15 +199,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                    : null;

    private final Handler mHandler;
    private final MainThreadExecutor mExecutor;
    final Object mLock = new Object();
    private final ActivityStartMonitor mActivityStartMonitor;

    public SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent,
            @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
        Log.i(TAG, "Initializing Activity Embedding Controller.");
        final MainThreadExecutor executor = new MainThreadExecutor();
        mHandler = executor.mHandler;
        mPresenter = new SplitPresenter(executor, windowLayoutComponent, this);
        mExecutor = new MainThreadExecutor();
        mHandler = mExecutor.mHandler;
        mPresenter = new SplitPresenter(mExecutor, windowLayoutComponent, this);
        mTransactionManager = new TransactionManager(mPresenter);
        final ActivityThread activityThread = ActivityThread.currentActivityThread();
        final Application application = activityThread.getApplication();
@@ -844,7 +849,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        // Checks if container should be updated before apply new parentInfo.
        final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo);
        taskContainer.updateTaskFragmentParentInfo(parentInfo);
        taskContainer.updateDivider(wct);

        // The divider need to be updated even if shouldUpdateContainer is false, because the decor
        // surface may change in TaskFragmentParentInfo, which requires divider update but not
        // container update.
        updateDivider(wct, taskContainer);

        // If the last direct activity of the host task is dismissed and the overlay container is
        // the only taskFragment, the overlay container should also be dismissed.
@@ -1007,6 +1016,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            if (taskContainer.isEmpty()) {
                // Cleanup the TaskContainer if it becomes empty.
                mTaskContainers.remove(taskContainer.getTaskId());
                mDividerPresenters.remove(taskContainer.getTaskId());
            }
            return;
        }
@@ -1759,6 +1769,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        }
        if (!mTaskContainers.contains(taskId)) {
            mTaskContainers.put(taskId, new TaskContainer(taskId, activityInTask));
            mDividerPresenters.put(taskId, new DividerPresenter(taskId, this, mExecutor));
        }
        final TaskContainer taskContainer = mTaskContainers.get(taskId);
        final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
@@ -3065,4 +3076,46 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        return configuration != null
                && configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
    }

    @GuardedBy("mLock")
    void updateDivider(
            @NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) {
        final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId());
        final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo();
        if (parentInfo != null) {
            dividerPresenter.updateDivider(
                    wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer());
        }
    }

    @Override
    public void onStartDragging(@NonNull Consumer<WindowContainerTransaction> action) {
        synchronized (mLock) {
            final TransactionRecord transactionRecord =
                    mTransactionManager.startNewTransaction();
            final WindowContainerTransaction wct = transactionRecord.getTransaction();
            action.accept(wct);
            transactionRecord.apply(false /* shouldApplyIndependently */);
        }
    }

    @Override
    public void onFinishDragging(
            int taskId,
            @NonNull Consumer<WindowContainerTransaction> action) {
        synchronized (mLock) {
            final TransactionRecord transactionRecord =
                    mTransactionManager.startNewTransaction();
            final WindowContainerTransaction wct = transactionRecord.getTransaction();
            final TaskContainer taskContainer = mTaskContainers.get(taskId);
            if (taskContainer != null) {
                final DividerPresenter dividerPresenter =
                        mDividerPresenters.get(taskContainer.getTaskId());
                taskContainer.updateTopSplitContainerForDivider(dividerPresenter);
                updateContainersInTask(wct, taskContainer);
            }
            action.accept(wct);
            transactionRecord.apply(false /* shouldApplyIndependently */);
        }
    }
}
+16 −16
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package androidx.window.extensions.embedding;
import static android.content.pm.PackageManager.MATCH_ALL;

import static androidx.window.extensions.embedding.DividerPresenter.getBoundsOffsetForDivider;
import static androidx.window.extensions.embedding.SplitAttributesHelper.isReversedLayout;
import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;

import android.app.Activity;
@@ -33,7 +34,6 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.util.LayoutDirection;
import android.util.Pair;
import android.util.Size;
import android.view.View;
@@ -368,7 +368,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
        updateAnimationParams(wct, primaryContainer.getTaskFragmentToken(), splitAttributes);
        updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
        taskContainer.updateDivider(wct);
        mController.updateDivider(wct, taskContainer);
    }

    private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@@ -697,6 +697,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        return RESULT_NOT_EXPANDED;
    }

    /**
     * Expands an existing TaskFragment to fill parent.
     * @param wct WindowContainerTransaction in which the task fragment should be resized.
     * @param container the {@link TaskFragmentContainer} to be expanded.
     */
    void expandTaskFragment(@NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentContainer container) {
        super.expandTaskFragment(wct, container);
        mController.updateDivider(wct, container.getTaskContainer());
    }

    static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) {
        return shouldShowSplit(splitContainer.getCurrentSplitAttributes());
    }
@@ -1108,7 +1119,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
     */
    private SplitType computeSplitType(@NonNull SplitAttributes splitAttributes,
            @NonNull Configuration taskConfiguration, @Nullable FoldingFeature foldingFeature) {
        final int layoutDirection = splitAttributes.getLayoutDirection();
        final SplitType splitType = splitAttributes.getSplitType();
        if (splitType instanceof ExpandContainersSplitType) {
            return splitType;
@@ -1117,19 +1127,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
            // Reverse the ratio for RIGHT_TO_LEFT and BOTTOM_TO_TOP to make the boundary
            // computation have the same direction, which is from (top, left) to (bottom, right).
            final SplitType reversedSplitType = new RatioSplitType(1 - splitRatio.getRatio());
            switch (layoutDirection) {
                case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT:
                case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM:
                    return splitType;
                case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT:
                case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP:
                    return reversedSplitType;
                case LayoutDirection.LOCALE: {
                    boolean isLtr = taskConfiguration.getLayoutDirection()
                            == View.LAYOUT_DIRECTION_LTR;
                    return isLtr ? splitType : reversedSplitType;
                }
            }
            return isReversedLayout(splitAttributes, taskConfiguration)
                    ? reversedSplitType
                    : splitType;
        } else if (splitType instanceof HingeSplitType) {
            final HingeSplitType hinge = (HingeSplitType) splitType;
            @WindowingMode
Loading