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

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

Merge "Fix resuming with focusable always-on-top stacks"

parents 5d4333c8 75016994
Loading
Loading
Loading
Loading
+36 −3
Original line number Diff line number Diff line
@@ -113,6 +113,13 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
     */
    private boolean mRemoved;

    /**
     * A focusable stack that is purposely to be positioned at the top. Although the stack may not
     * have the topmost index, it is used as a preferred candidate to prevent being unable to resume
     * target stack properly when there are other focusable always-on-top stacks.
     */
    private ActivityStack mPreferredTopFocusableStack;

    // Cached reference to some special stacks we tend to get a lot so we don't need to loop
    // through the list to find them.
    private ActivityStack mHomeStack = null;
@@ -165,6 +172,9 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
        if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack
                + " from displayId=" + mDisplayId);
        mStacks.remove(stack);
        if (mPreferredTopFocusableStack == stack) {
            mPreferredTopFocusableStack = null;
        }
        removeStackReferenceIfNeeded(stack);
        releaseSelfIfNeeded();
        mSupervisor.mService.updateSleepIfNeededLocked();
@@ -186,9 +196,21 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
    private void positionChildAt(ActivityStack stack, int position, boolean includingParents) {
        // TODO: Keep in sync with WindowContainer.positionChildAt(), once we change that to adjust
        //       the position internally, also update the logic here
        mStacks.remove(stack);
        final boolean wasContained = mStacks.remove(stack);
        final int insertPosition = getTopInsertPosition(stack, position);
        mStacks.add(insertPosition, stack);

        // The insert position may be adjusted to non-top when there is always-on-top stack. Since
        // the original position is preferred to be top, the stack should have higher priority when
        // we are looking for top focusable stack. The condition {@code wasContained} restricts the
        // preferred stack is set only when moving an existing stack to top instead of adding a new
        // stack that may be too early (e.g. in the middle of launching or reparenting).
        if (wasContained && position >= mStacks.size() - 1 && stack.isFocusableAndVisible()) {
            mPreferredTopFocusableStack = stack;
        } else if (mPreferredTopFocusableStack == stack) {
            mPreferredTopFocusableStack = null;
        }

        // Since positionChildAt() is called during the creation process of pinned stacks,
        // ActivityStack#getWindowContainerController() can be null. In this special case,
        // since DisplayContest#positionStackAt() is called in TaskStack#onConfigurationChanged(),
@@ -357,10 +379,18 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
                        this, stackId, mSupervisor, windowingMode, activityType, onTop);
    }

    /**
     * Get the preferred focusable stack in priority. If the preferred stack does not exist, find a
     * focusable and visible stack from the top of stacks in this display.
     */
    ActivityStack getFocusedStack() {
        if (mPreferredTopFocusableStack != null) {
            return mPreferredTopFocusableStack;
        }

        for (int i = mStacks.size() - 1; i >= 0; --i) {
            final ActivityStack stack = mStacks.get(i);
            if (stack.isFocusable() && stack.shouldBeVisible(null /* starting */)) {
            if (stack.isFocusableAndVisible()) {
                return stack;
            }
        }
@@ -382,7 +412,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
            if (ignoreCurrent && stack == currentFocus) {
                continue;
            }
            if (!stack.isFocusable() || !stack.shouldBeVisible(null)) {
            if (!stack.isFocusableAndVisible()) {
                continue;
            }

@@ -1105,6 +1135,9 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
        if (mSplitScreenPrimaryStack != null) {
            pw.println(myPrefix + "mSplitScreenPrimaryStack=" + mSplitScreenPrimaryStack);
        }
        if (mPreferredTopFocusableStack != null) {
            pw.println(myPrefix + "mPreferredTopFocusableStack=" + mPreferredTopFocusableStack);
        }
    }

    public void dumpStacks(PrintWriter pw) {
+4 −0
Original line number Diff line number Diff line
@@ -1146,6 +1146,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
        return mStackSupervisor.isFocusable(this, r != null && r.isFocusable());
    }

    boolean isFocusableAndVisible() {
        return isFocusable() && shouldBeVisible(null /* starting */);
    }

    final boolean isAttached() {
        return getParent() != null;
    }
+8 −7
Original line number Diff line number Diff line
@@ -733,10 +733,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
    }

    ActivityRecord getTopResumedActivity() {
        if (mWindowManager == null) {
            return null;
        }

        final ActivityStack focusedStack = getTopDisplayFocusedStack();
        if (focusedStack == null) {
            return null;
@@ -2284,7 +2280,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
            return false;
        }

        if (targetStack != null && targetStack.isTopStackOnDisplay()) {
        if (targetStack != null && (targetStack.isTopStackOnDisplay()
                || getTopDisplayFocusedStack() == targetStack)) {
            return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
        }

@@ -3982,8 +3979,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
    }

    public void dump(PrintWriter pw, String prefix) {
        pw.print(prefix); pw.print("mFocusedStack=" + getTopDisplayFocusedStack());
                pw.print(" mLastFocusedStack="); pw.println(mLastFocusedStack);
        pw.println();
        pw.println("ActivityStackSupervisor state:");
        pw.print(prefix);
        pw.println("topDisplayFocusedStack=" + getTopDisplayFocusedStack());
        pw.print(prefix);
        pw.println("mLastFocusedStack=" + mLastFocusedStack);
        pw.print(prefix);
        pw.println("mCurTaskIdForUser=" + mCurTaskIdForUser);
        pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront);
+115 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.server.am;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;

import static com.android.server.am.ActivityStackSupervisor.ON_TOP;

import static org.junit.Assert.assertTrue;

import android.platform.test.annotations.Presubmit;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

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

/**
 * Tests for the {@link ActivityDisplay} class.
 *
 * Build/Install/Run:
 *  atest WmTests:ActivityDisplayTests
 */
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityDisplayTests extends ActivityTestsBase {

    @Before
    @Override
    public void setUp() throws Exception {
        super.setUp();
        setupActivityTaskManagerService();
    }

    /**
     * This test simulates the picture-in-picture menu activity launches an activity to fullscreen
     * stack. The fullscreen stack should be the top focused for resuming correctly.
     */
    @Test
    public void testFullscreenStackCanBeFocusedWhenFocusablePinnedStackExists() {
        // Create a pinned stack and move to front.
        final ActivityStack pinnedStack = mSupervisor.getDefaultDisplay().createStack(
                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP);
        final TaskRecord pinnedTask = new TaskBuilder(mService.mStackSupervisor)
                .setStack(pinnedStack).build();
        new ActivityBuilder(mService).setActivityFlags(FLAG_ALWAYS_FOCUSABLE)
                .setTask(pinnedTask).build();
        pinnedStack.moveToFront("movePinnedStackToFront");

        // The focused stack should be the pinned stack.
        assertTrue(pinnedStack.isFocusedStackOnDisplay());

        // Create a fullscreen stack and move to front.
        final ActivityStack fullscreenStack = createFullscreenStackWithSimpleActivityAt(
                mSupervisor.getDefaultDisplay());
        fullscreenStack.moveToFront("moveFullscreenStackToFront");

        // The focused stack should be the fullscreen stack.
        assertTrue(fullscreenStack.isFocusedStackOnDisplay());
    }

    /**
     * Test {@link ActivityDisplay#mPreferredTopFocusableStack} will be cleared when the stack is
     * removed or moved to back, and the focused stack will be according to z-order.
     */
    @Test
    public void testStackShouldNotBeFocusedAfterMovingToBackOrRemoving() {
        // Create a display which only contains 2 stacks.
        final ActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
        final ActivityStack stack1 = createFullscreenStackWithSimpleActivityAt(display);
        final ActivityStack stack2 = createFullscreenStackWithSimpleActivityAt(display);

        // Put stack1 and stack2 on top.
        stack1.moveToFront("moveStack1ToFront");
        stack2.moveToFront("moveStack2ToFront");
        assertTrue(stack2.isFocusedStackOnDisplay());

        // Stack1 should be focused after moving stack2 to back.
        stack2.moveToBack("moveStack2ToBack", null /* task */);
        assertTrue(stack1.isFocusedStackOnDisplay());

        // Stack2 should be focused after removing stack1.
        display.removeChild(stack1);
        assertTrue(stack2.isFocusedStackOnDisplay());
    }

    private ActivityStack createFullscreenStackWithSimpleActivityAt(ActivityDisplay display) {
        final ActivityStack fullscreenStack = display.createStack(
                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP);
        final TaskRecord fullscreenTask = new TaskBuilder(mService.mStackSupervisor)
                .setStack(fullscreenStack).build();
        new ActivityBuilder(mService).setTask(fullscreenTask).build();
        return fullscreenStack;
    }
}
+31 −1
Original line number Diff line number Diff line
@@ -33,10 +33,12 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyInt;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

@@ -403,4 +405,32 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase {
        assertEquals(primaryStack.getBounds(), STACK_SIZE);
        assertEquals(task.getBounds(), TASK_SIZE);
    }

    /**
     * Verify if a stack is not at the topmost position, it should be able to resume its activity if
     * the stack is the top focused.
     */
    @Test
    public void testResumeActivityWhenNonTopmostStackIsTopFocused() throws Exception {
        // Create a stack at bottom.
        final ActivityDisplay display = mSupervisor.getDefaultDisplay();
        final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN,
                ACTIVITY_TYPE_STANDARD, false /* onTop */));
        final TaskRecord task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
        final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build();
        display.positionChildAtBottom(targetStack);

        // Assume the stack is not at the topmost position (e.g. behind always-on-top stacks) but it
        // is the current top focused stack.
        assertFalse(targetStack.isTopStackOnDisplay());
        doReturn(targetStack).when(mSupervisor).getTopDisplayFocusedStack();

        // Use the stack as target to resume.
        mSupervisor.resumeFocusedStacksTopActivitiesLocked(
                targetStack, activity, null /* targetOptions */);

        // Verify the target stack should resume its activity.
        verify(targetStack, times(1)).resumeTopActivityUncheckedLocked(
                eq(activity), eq(null /* targetOptions */));
    }
}