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

Commit 3cbdebae authored by Chris Li's avatar Chris Li Committed by Automerger Merge Worker
Browse files

Merge "Fix crash in getParentContainerBounds" into tm-dev am: 701b3f75

parents 801af429 701b3f75
Loading
Loading
Loading
Loading
+36 −57
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
@@ -59,6 +60,7 @@ import java.util.function.Consumer;
 */
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
        ActivityEmbeddingComponent {
    private static final String TAG = "SplitController";

    @VisibleForTesting
    final SplitPresenter mPresenter;
@@ -229,8 +231,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            }
            if (taskContainer.isEmpty()) {
                // Cleanup the TaskContainer if it becomes empty.
                mPresenter.stopOverrideSplitAnimation(taskContainer.mTaskId);
                mTaskContainers.remove(taskContainer.mTaskId);
                mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
                mTaskContainers.remove(taskContainer.getTaskId());
            }
            return;
        }
@@ -241,13 +243,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        if (taskContainer == null) {
            return;
        }
        final boolean wasInPip = isInPictureInPicture(taskContainer.mConfiguration);
        final boolean wasInPip = isInPictureInPicture(taskContainer.getConfiguration());
        final boolean isInPIp = isInPictureInPicture(config);
        taskContainer.mConfiguration = config;
        taskContainer.setConfiguration(config);

        // We need to check the animation override when enter/exit PIP or has bounds changed.
        boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
        if (onTaskBoundsMayChange(taskContainer, config.windowConfiguration.getBounds())
        if (taskContainer.setTaskBounds(config.windowConfiguration.getBounds())
                && !isInPIp) {
            // We don't care the bounds change when it has already entered PIP.
            shouldUpdateAnimationOverride = true;
@@ -257,16 +259,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        }
    }

    /** Returns {@code true} if the bounds is changed. */
    private boolean onTaskBoundsMayChange(@NonNull TaskContainer taskContainer,
            @NonNull Rect taskBounds) {
        if (!taskBounds.isEmpty() && !taskContainer.mTaskBounds.equals(taskBounds)) {
            taskContainer.mTaskBounds.set(taskBounds);
            return true;
        }
        return false;
    }

    /**
     * Updates if we should override transition animation. We only want to override if the Task
     * bounds is large enough for at least one split rule.
@@ -279,15 +271,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen

        // We only want to override if it supports split.
        if (supportSplit(taskContainer)) {
            mPresenter.startOverrideSplitAnimation(taskContainer.mTaskId);
            mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
        } else {
            mPresenter.stopOverrideSplitAnimation(taskContainer.mTaskId);
            mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
        }
    }

    private boolean supportSplit(@NonNull TaskContainer taskContainer) {
        // No split inside PIP.
        if (isInPictureInPicture(taskContainer.mConfiguration)) {
        if (isInPictureInPicture(taskContainer.getConfiguration())) {
            return false;
        }
        // Check if the parent container bounds can support any split rule.
@@ -295,7 +287,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            if (!(rule instanceof SplitRule)) {
                continue;
            }
            if (mPresenter.shouldShowSideBySide(taskContainer.mTaskBounds, (SplitRule) rule)) {
            if (mPresenter.shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) {
                return true;
            }
        }
@@ -425,21 +417,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        return null;
    }

    TaskFragmentContainer newContainer(@NonNull Activity activity, int taskId) {
        return newContainer(activity, activity, taskId);
    }

    /**
     * Creates and registers a new organized container with an optional activity that will be
     * re-parented to it in a WCT.
     *
     * @param activity          the activity that will be reparented to the TaskFragment.
     * @param activityInTask    activity in the same Task so that we can get the Task bounds if
     *                          needed.
     * @param taskId            parent Task of the new TaskFragment.
     */
    TaskFragmentContainer newContainer(@Nullable Activity activity, int taskId) {
    TaskFragmentContainer newContainer(@Nullable Activity activity,
            @NonNull Activity activityInTask, int taskId) {
        if (activityInTask == null) {
            throw new IllegalArgumentException("activityInTask must not be null,");
        }
        final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId);
        if (!mTaskContainers.contains(taskId)) {
            mTaskContainers.put(taskId, new TaskContainer(taskId));
        }
        final TaskContainer taskContainer = mTaskContainers.get(taskId);
        taskContainer.mContainers.add(container);
        if (activity != null && !taskContainer.isTaskBoundsInitialized()
                && onTaskBoundsMayChange(taskContainer,
                SplitPresenter.getTaskBoundsFromActivity(activity))) {
            // Initial check before any TaskFragment has appeared.
        if (!taskContainer.isTaskBoundsInitialized()) {
            // Get the initial bounds before the TaskFragment has appeared.
            final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask);
            if (!taskContainer.setTaskBounds(taskBounds)) {
                Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
            }
            updateAnimationOverride(taskContainer);
        }
        return container;
@@ -887,6 +894,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        return null;
    }

    @Nullable
    TaskContainer getTaskContainer(int taskId) {
        return mTaskContainers.get(taskId);
    }

    /**
     * Returns {@code true} if an Activity with the provided component name should always be
     * expanded to occupy full task bounds. Such activity must not be put in a split.
@@ -1211,37 +1223,4 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        return configuration != null
                && configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
    }

    /** Represents TaskFragments and split pairs below a Task. */
    @VisibleForTesting
    static class TaskContainer {
        /** The unique task id. */
        final int mTaskId;
        /** Active TaskFragments in this Task. */
        final List<TaskFragmentContainer> mContainers = new ArrayList<>();
        /** Active split pairs in this Task. */
        final List<SplitContainer> mSplitContainers = new ArrayList<>();
        /**
         * TaskFragments that the organizer has requested to be closed. They should be removed when
         * the organizer receives {@link #onTaskFragmentVanished(TaskFragmentInfo)} event for them.
         */
        final Set<IBinder> mFinishedContainer = new ArraySet<>();
        /** Available window bounds of this Task. */
        final Rect mTaskBounds = new Rect();
        /** Configuration of the Task. */
        @Nullable
        Configuration mConfiguration;

        TaskContainer(int taskId) {
            mTaskId = taskId;
        }

        boolean isEmpty() {
            return mContainers.isEmpty() && mFinishedContainer.isEmpty();
        }

        boolean isTaskBoundsInitialized() {
            return !mTaskBounds.isEmpty();
        }
    }
}
+17 −28
Original line number Diff line number Diff line
@@ -19,9 +19,9 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;

import android.app.Activity;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -111,8 +111,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
                primaryActivity, primaryRectBounds, null);

        // Create new empty task fragment
        final TaskFragmentContainer secondaryContainer = mController.newContainer(null,
                primaryContainer.getTaskId());
        final TaskFragmentContainer secondaryContainer = mController.newContainer(
                null /* activity */, primaryActivity, primaryContainer.getTaskId());
        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
                rule, isLtr(primaryActivity, rule));
        createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
@@ -168,8 +168,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
     * Creates a new expanded container.
     */
    TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) {
        final TaskFragmentContainer newContainer = mController.newContainer(null,
                launchingActivity.getTaskId());
        final TaskFragmentContainer newContainer = mController.newContainer(null /* activity */,
                launchingActivity, launchingActivity.getTaskId());

        final WindowContainerTransaction wct = new WindowContainerTransaction();
        createTaskFragment(wct, newContainer.getTaskFragmentToken(),
@@ -236,8 +236,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
                    launchingActivity.getTaskId());
        }

        TaskFragmentContainer secondaryContainer = mController.newContainer(null,
                primaryContainer.getTaskId());
        TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */,
                launchingActivity, primaryContainer.getTaskId());
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
                rule);
@@ -398,20 +398,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {

    @NonNull
    Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) {
        final Configuration parentConfig = mFragmentParentConfigs.get(
                container.getTaskFragmentToken());
        if (parentConfig != null) {
            return parentConfig.windowConfiguration.getBounds();
        }

        // If there is no parent yet - then assuming that activities are running in full task bounds
        final Activity topActivity = container.getTopNonFinishingActivity();
        final Rect bounds = topActivity != null ? getParentContainerBounds(topActivity) : null;

        if (bounds == null) {
            throw new IllegalStateException("Unknown parent bounds");
        final int taskId = container.getTaskId();
        final TaskContainer taskContainer = mController.getTaskContainer(taskId);
        if (taskContainer == null) {
            throw new IllegalStateException("Can't find TaskContainer taskId=" + taskId);
        }
        return bounds;
        return taskContainer.getTaskBounds();
    }

    @NonNull
@@ -419,22 +411,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        final TaskFragmentContainer container = mController.getContainerWithActivity(
                activity.getActivityToken());
        if (container != null) {
            final Configuration parentConfig = mFragmentParentConfigs.get(
                    container.getTaskFragmentToken());
            if (parentConfig != null) {
                return parentConfig.windowConfiguration.getBounds();
            }
            return getParentContainerBounds(container);
        }

        return getTaskBoundsFromActivity(activity);
    }

    @NonNull
    static Rect getTaskBoundsFromActivity(@NonNull Activity activity) {
        final WindowConfiguration windowConfiguration =
                activity.getResources().getConfiguration().windowConfiguration;
        if (!activity.isInMultiWindowMode()) {
            // In fullscreen mode the max bounds should correspond to the task bounds.
            return activity.getResources().getConfiguration().windowConfiguration.getMaxBounds();
            return windowConfiguration.getMaxBounds();
        }
        return activity.getResources().getConfiguration().windowConfiguration.getBounds();
        return windowConfiguration.getBounds();
    }
}
+97 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
import android.window.TaskFragmentInfo;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/** Represents TaskFragments and split pairs below a Task. */
class TaskContainer {

    /** The unique task id. */
    private final int mTaskId;

    /** Available window bounds of this Task. */
    private final Rect mTaskBounds = new Rect();

    /** Configuration of the Task. */
    @Nullable
    private Configuration mConfiguration;

    /** Active TaskFragments in this Task. */
    final List<TaskFragmentContainer> mContainers = new ArrayList<>();

    /** Active split pairs in this Task. */
    final List<SplitContainer> mSplitContainers = new ArrayList<>();

    /**
     * TaskFragments that the organizer has requested to be closed. They should be removed when
     * the organizer receives {@link SplitController#onTaskFragmentVanished(TaskFragmentInfo)} event
     * for them.
     */
    final Set<IBinder> mFinishedContainer = new ArraySet<>();

    TaskContainer(int taskId) {
        mTaskId = taskId;
    }

    int getTaskId() {
        return mTaskId;
    }

    @NonNull
    Rect getTaskBounds() {
        return mTaskBounds;
    }

    /** Returns {@code true} if the bounds is changed. */
    boolean setTaskBounds(@NonNull Rect taskBounds) {
        if (!taskBounds.isEmpty() && !mTaskBounds.equals(taskBounds)) {
            mTaskBounds.set(taskBounds);
            return true;
        }
        return false;
    }

    /** Whether the Task bounds has been initialized. */
    boolean isTaskBoundsInitialized() {
        return !mTaskBounds.isEmpty();
    }

    @Nullable
    Configuration getConfiguration() {
        return mConfiguration;
    }

    void setConfiguration(@Nullable Configuration configuration) {
        mConfiguration = configuration;
    }

    /** Whether there is any {@link TaskFragmentContainer} below this Task. */
    boolean isEmpty() {
        return mContainers.isEmpty() && mFinishedContainer.isEmpty();
    }
}
+25 −2
Original line number Diff line number Diff line
@@ -21,6 +21,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;

import static com.google.common.truth.Truth.assertWithMessage;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -29,12 +32,12 @@ import static org.mockito.Mockito.never;
import android.app.Activity;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.window.extensions.embedding.SplitController.TaskContainer;

import org.junit.Before;
import org.junit.Test;
@@ -53,6 +56,7 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class SplitControllerTest {
    private static final int TASK_ID = 10;
    private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);

    @Mock
    private Activity mActivity;
@@ -70,8 +74,11 @@ public class SplitControllerTest {
        mSplitPresenter = mSplitController.mPresenter;
        spyOn(mSplitController);
        spyOn(mSplitPresenter);
        final Configuration activityConfig = new Configuration();
        activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
        activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
        doReturn(mActivityResources).when(mActivity).getResources();
        doReturn(new Configuration()).when(mActivityResources).getConfiguration();
        doReturn(activityConfig).when(mActivityResources).getConfiguration();
    }

    @Test
@@ -117,4 +124,20 @@ public class SplitControllerTest {
        verify(mSplitController).removeContainer(tf);
        verify(mActivity, never()).finish();
    }

    @Test
    public void testNewContainer() {
        // Must pass in a valid activity.
        assertThrows(IllegalArgumentException.class, () ->
                mSplitController.newContainer(null /* activity */, TASK_ID));
        assertThrows(IllegalArgumentException.class, () ->
                mSplitController.newContainer(mActivity, null /* launchingActivity */, TASK_ID));

        final TaskFragmentContainer tf = mSplitController.newContainer(null, mActivity, TASK_ID);
        final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);

        assertNotNull(tf);
        assertNotNull(taskContainer);
        assertEquals(TASK_BOUNDS, taskContainer.getTaskBounds());
    }
}
+82 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Test class for {@link TaskContainer}.
 *
 * Build/Install/Run:
 *  atest WMJetpackUnitTests:TaskContainerTest
 */
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TaskContainerTest {
    private static final int TASK_ID = 10;
    private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);

    @Test
    public void testIsTaskBoundsInitialized() {
        final TaskContainer taskContainer = new TaskContainer(TASK_ID);

        assertFalse(taskContainer.isTaskBoundsInitialized());

        taskContainer.setTaskBounds(TASK_BOUNDS);

        assertTrue(taskContainer.isTaskBoundsInitialized());
    }

    @Test
    public void testSetTaskBounds() {
        final TaskContainer taskContainer = new TaskContainer(TASK_ID);

        assertFalse(taskContainer.setTaskBounds(new Rect()));

        assertTrue(taskContainer.setTaskBounds(TASK_BOUNDS));

        assertFalse(taskContainer.setTaskBounds(TASK_BOUNDS));
    }

    @Test
    public void testIsEmpty() {
        final TaskContainer taskContainer = new TaskContainer(TASK_ID);

        assertTrue(taskContainer.isEmpty());

        final TaskFragmentContainer tf = new TaskFragmentContainer(null, TASK_ID);
        taskContainer.mContainers.add(tf);

        assertFalse(taskContainer.isEmpty());

        taskContainer.mFinishedContainer.add(tf.getTaskFragmentToken());
        taskContainer.mContainers.clear();

        assertFalse(taskContainer.isEmpty());
    }
}