/*
 * Copyright (C) 2019 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.wm;

import static android.Manifest.permission.REPOSITION_SELF_WINDOWS;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT;
import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_FIRST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.window.flags.Flags.FLAG_ENABLE_SYS_DECORS_CALLBACKS_VIA_WM;
import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOW_REPOSITIONING_API;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
import android.app.HandoffActivityData;
import android.app.HandoffFailureCode;
import android.app.IApplicationThread;
import android.app.IHandoffTaskDataReceiver;
import android.app.PictureInPictureParams;
import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.EnterPipRequestedItem;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.IDisplayWindowListener;
import android.view.WindowManager;

import androidx.test.filters.MediumTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * Tests for the {@link ActivityTaskManagerService} class.
 *
 * Build/Install/Run:
 *  atest WmTests:ActivityTaskManagerServiceTests
 */
@Presubmit
@MediumTest
@RunWith(WindowTestRunner.class)
public class ActivityTaskManagerServiceTests extends WindowTestsBase {

    private static final String DEFAULT_PACKAGE_NAME = "my.application.package";
    private static final int DEFAULT_USER_ID = 100;

    private static class TestDisplayWindowListenerBase extends IDisplayWindowListener.Stub {
        @Override
        public void onDisplayAdded(int displayId) {}

        @Override
        public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {}

        @Override
        public void onDisplayRemoved(int displayId) {}

        @Override
        public void onFixedRotationStarted(int displayId, int newRotation) {}

        @Override
        public void onFixedRotationFinished(int displayId) {}

        @Override
        public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
                List<Rect> unrestricted) {}

        @Override
        public void onDesktopModeEligibleChanged(int displayId) {}

        @Override
        public void onDisplayAddSystemDecorations(int displayId) {}

        @Override
        public void onDisplayRemoveSystemDecorations(int displayId) {}

        @Override
        public void onDisplayAnimationsDisabledChanged(int displayId, boolean disabled) {}
    }

    private static class TestHandoffTaskDataReceiver extends IHandoffTaskDataReceiver.Stub {

        private final List<Integer> receivedTaskIds = new ArrayList<>();
        private final List<List<HandoffActivityData>> receivedHandoffActivityData =
                new ArrayList<>();
        private final List<Integer> receivedErrorCodes = new ArrayList<>();

        @Override
        public void onHandoffTaskDataRequestSucceeded(
            int taskId,
            List<HandoffActivityData> handoffActivityData
        ) {
            receivedTaskIds.add(taskId);
            receivedHandoffActivityData.add(handoffActivityData);
        }

        @Override
        public void onHandoffTaskDataRequestFailed(int taskId, int errorCode) {
            receivedTaskIds.add(taskId);
            receivedErrorCodes.add(errorCode);
        }

        public void verifySucceeded(
            int expectedTaskId,
            HandoffActivityData expectedHandoffActivityData) {
            assertEquals(receivedTaskIds.size(), 1);
            assertEquals((int) receivedTaskIds.get(0), expectedTaskId);
            assertEquals(receivedHandoffActivityData.size(), 1);
            assertEquals(receivedHandoffActivityData.get(0).size(), 1);
            assertEquals(receivedHandoffActivityData.get(0).get(0), expectedHandoffActivityData);
        }

        public void verifyFailed(int expectedTaskId, int expectedErrorCode) {
            assertEquals(receivedTaskIds.size(), 1);
            assertEquals((int) receivedTaskIds.get(0), expectedTaskId);
            assertEquals(receivedErrorCodes.size(), 1);
            assertEquals((int) receivedErrorCodes.get(0), expectedErrorCode);
        }
    }

    @Before
    public void setUp() throws Exception {
        setBooted(mAtm);
    }

    /** Verify that activity is finished correctly upon request. */
    @Test
    public void testActivityFinish() {
        final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
        assertTrue("Activity must be finished", mAtm.mActivityClientController.finishActivity(
                activity.token, 0 /* resultCode */, null /* resultData */,
                Activity.DONT_FINISH_TASK_WITH_ACTIVITY));
        assertTrue(activity.finishing);

        assertTrue("Duplicate activity finish request must also return 'true'",
                mAtm.mActivityClientController.finishActivity(activity.token, 0 /* resultCode */,
                        null /* resultData */, Activity.DONT_FINISH_TASK_WITH_ACTIVITY));
    }

    @Test
    public void testOnPictureInPictureRequested() throws RemoteException {
        final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
        final ClientLifecycleManager mockLifecycleManager = mock(ClientLifecycleManager.class);
        doReturn(mockLifecycleManager).when(mAtm).getLifecycleManager();
        doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
        clearInvocations(mClientLifecycleManager);

        mAtm.mActivityClientController.requestPictureInPictureMode(activity);

        final ArgumentCaptor<ClientTransactionItem> clientTransactionItemCaptor =
                ArgumentCaptor.forClass(ClientTransactionItem.class);
        verify(mockLifecycleManager).scheduleTransactionItem(any(),
                clientTransactionItemCaptor.capture());
        final ClientTransactionItem transactionItem = clientTransactionItemCaptor.getValue();
        // Check that only an enter pip request item callback was scheduled.
        assertTrue(transactionItem instanceof EnterPipRequestedItem);
    }

    @Test
    public void testOnPictureInPictureRequested_cannotEnterPip() throws RemoteException {
        final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
        doReturn(false).when(activity).inPinnedWindowingMode();
        doReturn(false).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
        clearInvocations(mClientLifecycleManager);

        mAtm.mActivityClientController.requestPictureInPictureMode(activity);

        verify(mClientLifecycleManager, never()).scheduleTransactionItem(any(), any());
    }

    @Test
    public void testOnPictureInPictureRequested_alreadyInPIPMode() throws RemoteException {
        final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
        doReturn(true).when(activity).inPinnedWindowingMode();
        clearInvocations(mClientLifecycleManager);

        mAtm.mActivityClientController.requestPictureInPictureMode(activity);

        verify(mClientLifecycleManager, never()).scheduleTransactionItem(any(), any());
    }

    @Test
    public void testAddHandoffEnablementListener_doesNotNotifyIfFlagDisabled() {
        ActivityTaskManagerInternal.HandoffEnablementListener handoffEnablementListener =
                mock(ActivityTaskManagerInternal.HandoffEnablementListener.class);
        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = task.getTopNonFinishingActivity();
        mAtm.getAtmInternal().registerHandoffEnablementListener(handoffEnablementListener);
        activity.setHandoffEnabled(true, true);
        verify(handoffEnablementListener, never())
            .onHandoffEnabledChanged(activity.getRootTaskId(), true);
        activity.setHandoffEnabled(false, true);
        verify(handoffEnablementListener, never())
            .onHandoffEnabledChanged(activity.getRootTaskId(), false);
    }

    @Test
    @EnableFlags(android.companion.Flags.FLAG_ENABLE_TASK_CONTINUITY)
    public void testRegisterHandoffEnablementListener_notifiesListenerOnChange() {
        ActivityTaskManagerInternal.HandoffEnablementListener handoffEnablementListener =
                mock(ActivityTaskManagerInternal.HandoffEnablementListener.class);
        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = task.getTopNonFinishingActivity();
        mAtm.getAtmInternal().registerHandoffEnablementListener(handoffEnablementListener);
        activity.setHandoffEnabled(true, true);
        verify(handoffEnablementListener)
            .onHandoffEnabledChanged(anyInt(), anyBoolean());
        activity.setHandoffEnabled(false, true);
        verify(handoffEnablementListener)
            .onHandoffEnabledChanged(activity.getRootTaskId(), false);
    }

    @Test
    @EnableFlags(android.companion.Flags.FLAG_ENABLE_TASK_CONTINUITY)
    public void testUnregisterHandoffEnablementListener_doesNotNotifyListenerOnChange() {
        ActivityTaskManagerInternal.HandoffEnablementListener handoffEnablementListener =
                mock(ActivityTaskManagerInternal.HandoffEnablementListener.class);
        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = task.getTopNonFinishingActivity();
        mAtm.getAtmInternal().registerHandoffEnablementListener(handoffEnablementListener);
        mAtm.getAtmInternal().unregisterHandoffEnablementListener(handoffEnablementListener);
        activity.setHandoffEnabled(true, true);
        verify(handoffEnablementListener, never())
            .onHandoffEnabledChanged(activity.getRootTaskId(), true);
        activity.setHandoffEnabled(false, true);
        verify(handoffEnablementListener, never())
            .onHandoffEnabledChanged(activity.getRootTaskId(), false);
    }

    @Test
    public void testIsHandoffEnabledForTask_returnsFalseIfFlagDisabled() {
        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = task.getTopNonFinishingActivity();
        doReturn(true).when(activity).isHandoffEnabled();
        assertFalse(mAtm.getAtmInternal().isHandoffEnabledForTask(task.getRootTaskId()));
    }

    @Test
    @EnableFlags(android.companion.Flags.FLAG_ENABLE_TASK_CONTINUITY)
    public void testIsHandoffEnabledForTask_returnsTrueIfHandoffEnabled() {
        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = task.getTopNonFinishingActivity();
        activity.setHandoffEnabled(true, true);
        assertTrue(mAtm.getAtmInternal().isHandoffEnabledForTask(task.getRootTaskId()));
        activity.setHandoffEnabled(false, true);
        assertFalse(mAtm.getAtmInternal().isHandoffEnabledForTask(task.getRootTaskId()));
    }

    @Test
    @EnableFlags(android.companion.Flags.FLAG_ENABLE_TASK_CONTINUITY)
    public void testIsHandoffEnabledForTask_returnsFalseIfNoSuchTask() {
        assertFalse(mAtm.getAtmInternal().isHandoffEnabledForTask(1000));
    }

    @Test
    public void testRequestHandoffTaskData_failsIfFlagDisabled() {
        // Create a test task.
        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();

        // Setup a fake receiver to receive the result.
        TestHandoffTaskDataReceiver receiver = new TestHandoffTaskDataReceiver();

        // Request Handoff
        mAtm.requestHandoffTaskData(task.getRootTaskId(), receiver);

        // Verify that the result code is failure.
        receiver.verifyFailed(task.getRootTaskId(),
                HandoffFailureCode.HANDOFF_FAILURE_UNSUPPORTED_DEVICE);
    }

    @EnableFlags(android.companion.Flags.FLAG_ENABLE_TASK_CONTINUITY)
    @Test
    public void testRequestHandoffTaskData_failsIfNoTaskWithId() {
        // Setup a fake receiver to receive the result.
        TestHandoffTaskDataReceiver receiver = new TestHandoffTaskDataReceiver();

        // Request Handoff
        mAtm.requestHandoffTaskData(0, receiver);

        // Verify that the result code is failure.
        receiver.verifyFailed(0, HandoffFailureCode.HANDOFF_FAILURE_UNKNOWN_TASK);
    }

    @EnableFlags(android.companion.Flags.FLAG_ENABLE_TASK_CONTINUITY)
    @Test
    public void testRequestHandoffTaskData_failsIfNoActivityInTask() {
        // Create a test task.
        final Task task = new TaskBuilder(mSupervisor).build();

        // Setup a fake receiver to receive the result.
        TestHandoffTaskDataReceiver receiver = new TestHandoffTaskDataReceiver();

        // Request Handoff
        mAtm.requestHandoffTaskData(task.getRootTaskId(), receiver);

        // Verify that the result code is failure.
        receiver.verifyFailed(
            task.getRootTaskId(),
            HandoffFailureCode.HANDOFF_FAILURE_EMPTY_TASK);
    }

    @EnableFlags(android.companion.Flags.FLAG_ENABLE_TASK_CONTINUITY)
    @Test
    public void testRequestHandoffTaskData_failsIfHandoffDisabledForActivity() {
        // Create a test task.
        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = task.getTopNonFinishingActivity();
        doReturn(false).when(activity).isHandoffEnabled();

        // Setup a fake receiver to receive the result.
        TestHandoffTaskDataReceiver receiver = new TestHandoffTaskDataReceiver();

        // Request Handoff
        mAtm.requestHandoffTaskData(task.getRootTaskId(), receiver);

        // Verify that the result code is failure.
        receiver.verifyFailed(
            task.getRootTaskId(),
            HandoffFailureCode.HANDOFF_FAILURE_UNSUPPORTED_TASK);
    }

    @EnableFlags(android.companion.Flags.FLAG_ENABLE_TASK_CONTINUITY)
    @Test
    public void testRequestHandoffTaskData_succeedsWithActivityInForeground()
        throws Exception{
        // Create a test task.
        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = task.getTopNonFinishingActivity();
        doReturn(true).when(activity).attachedToProcess();
        doReturn(true).when(activity).isState(RESUMED);
        WindowProcessController mockWindowProcessController = mock(WindowProcessController.class);
        activity.app = mockWindowProcessController;
        IApplicationThread mockThread = mock(IApplicationThread.class);
        doReturn(mockThread).when(mockWindowProcessController).getThread();
        doReturn(true).when(activity).isProcessRunning();
        doReturn(true).when(activity).isHandoffEnabled();

        // Setup a fake receiver to receive the result.
        TestHandoffTaskDataReceiver receiver = new TestHandoffTaskDataReceiver();

        // Request Handoff
        mAtm.requestHandoffTaskData(task.getRootTaskId(), receiver);

        ArgumentCaptor<IBinder> requestTokenCaptor = ArgumentCaptor.forClass(IBinder.class);
        ArgumentCaptor<List<IBinder>> activityTokenCaptor = ArgumentCaptor.forClass(List.class);
        verify(mockThread).requestHandoffActivityData(
            requestTokenCaptor.capture(),
            activityTokenCaptor.capture());

        // Finish the request
        HandoffActivityData handoffActivityData
            = new HandoffActivityData.Builder(new ComponentName("pkg", "cls"))
                .build();

        mAtm.reportHandoffActivityData(
            requestTokenCaptor.getValue(),
            List.of(handoffActivityData));

        // Verify that the result code is success.
        receiver.verifySucceeded(task.getRootTaskId(), handoffActivityData);
    }

    @EnableFlags(android.companion.Flags.FLAG_ENABLE_TASK_CONTINUITY)
    @Test
    public void testRequestHandoffTaskData_succeedsWithActivityInBackground()
        throws Exception{
        // Create a test task.
        final HandoffActivityData handoffActivityData
            = new HandoffActivityData.Builder(new ComponentName("pkg", "cls"))
                .build();
        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = task.getTopNonFinishingActivity();
        doReturn(false).when(activity).attachedToProcess();
        doReturn(handoffActivityData).when(activity).getHandoffActivityData();
        doReturn(false).when(activity).isProcessRunning();
        doReturn(true).when(activity).isHandoffEnabled();

        // Setup a fake receiver to receive the result.
        final TestHandoffTaskDataReceiver receiver = new TestHandoffTaskDataReceiver();

        // Request Handoff
        mAtm.requestHandoffTaskData(task.getRootTaskId(), receiver);

        // Verify that the result code is success.
        receiver.verifySucceeded(task.getRootTaskId(), handoffActivityData);
    }

    @EnableFlags(android.companion.Flags.FLAG_ENABLE_TASK_CONTINUITY)
    @Test
    public void testRequestHandoffTaskData_failsIfNoDataReturned()
        throws Exception{
        // Create a test task.
        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = task.getTopNonFinishingActivity();
        doReturn(true).when(activity).attachedToProcess();
        WindowProcessController mockWindowProcessController = mock(WindowProcessController.class);
        activity.app = mockWindowProcessController;
        IApplicationThread mockThread = mock(IApplicationThread.class);
        doReturn(mockThread).when(mockWindowProcessController).getThread();
        doReturn(true).when(activity).isProcessRunning();
        doReturn(true).when(activity).isHandoffEnabled();
        doReturn(true).when(activity).isState(RESUMED);

        // Setup a fake receiver to receive the result.
        TestHandoffTaskDataReceiver receiver = new TestHandoffTaskDataReceiver();

        // Request Handoff
        mAtm.requestHandoffTaskData(task.getRootTaskId(), receiver);

        ArgumentCaptor<IBinder> requestTokenCaptor = ArgumentCaptor.forClass(IBinder.class);
        ArgumentCaptor<List<IBinder>> activityTokenCaptor
            = ArgumentCaptor.forClass(List.class);
        verify(mockThread).requestHandoffActivityData(
            requestTokenCaptor.capture(),
            activityTokenCaptor.capture());

        // Finish the request
        List<HandoffActivityData> handoffActivityDataList = new ArrayList<>();
        handoffActivityDataList.add(null);
        mAtm.reportHandoffActivityData(
            requestTokenCaptor.getValue(),
            handoffActivityDataList);

        // Verify that the result code is failure.
        receiver.verifyFailed(
            task.getRootTaskId(),
            HandoffFailureCode.HANDOFF_FAILURE_APP_DID_NOT_REPORT_HANDOFF_DATA);
    }

    @Test
    public void testDisplayWindowListener() {
        final ArrayList<Integer> added = new ArrayList<>();
        final ArrayList<Integer> changed = new ArrayList<>();
        final ArrayList<Integer> removed = new ArrayList<>();
        IDisplayWindowListener listener = new TestDisplayWindowListenerBase() {
            @Override
            public void onDisplayAdded(int displayId) {
                added.add(displayId);
            }

            @Override
            public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
                changed.add(displayId);
            }

            @Override
            public void onDisplayRemoved(int displayId) {
                removed.add(displayId);
            }
        };
        int[] displayIds = mAtm.mWindowManager.registerDisplayWindowListener(listener);
        for (int i = 0; i < displayIds.length; i++) {
            added.add(displayIds[i]);
        }
        // Check that existing displays call added
        assertEquals(mRootWindowContainer.getChildCount(), added.size());
        assertEquals(0, changed.size());
        assertEquals(0, removed.size());
        added.clear();
        // Check adding a display
        DisplayContent newDisp1 = new TestDisplayContent.Builder(mAtm, 600, 800).build();
        assertEquals(1, added.size());
        assertEquals(0, changed.size());
        assertEquals(0, removed.size());
        added.clear();
        // Check that changes are reported
        Configuration c = new Configuration(newDisp1.getRequestedOverrideConfiguration());
        c.windowConfiguration.setBounds(new Rect(0, 0, 1000, 1300));
        newDisp1.performDisplayOverrideConfigUpdate(c);
        assertEquals(0, added.size());
        assertEquals(1, changed.size());
        assertEquals(0, removed.size());
        changed.clear();

        // Check that removal is reported
        newDisp1.remove();
        assertEquals(0, added.size());
        assertEquals(0, changed.size());
        assertEquals(1, removed.size());
    }

    @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
    @Test
    public void testDisplayWindowListener_desktopModeEligibleChanged() {
        final ArrayList<Integer> desktopModeEligibleChanged = new ArrayList<>();
        IDisplayWindowListener listener = new TestDisplayWindowListenerBase() {
            @Override
            public void onDesktopModeEligibleChanged(int displayId) {
                desktopModeEligibleChanged.add(displayId);
            }
        };
        mAtm.mWindowManager.registerDisplayWindowListener(listener);
        DisplayContent newDisp1 = new TestDisplayContent.Builder(mAtm, 600, 800).build();

        // Check adding decoration
        doReturn(true).when(newDisp1).allowContentModeSwitch();
        doReturn(true).when(newDisp1).isSystemDecorationsSupported();
        mAtm.mWindowManager.mDisplayWindowSettings.setShouldShowSystemDecorsLocked(newDisp1, true);
        assertEquals(1, desktopModeEligibleChanged.size());
        assertEquals(newDisp1.mDisplayId, (int) desktopModeEligibleChanged.get(0));
        desktopModeEligibleChanged.clear();
        // Check removing decoration
        doReturn(false).when(newDisp1).isSystemDecorationsSupported();
        mAtm.mWindowManager.mDisplayWindowSettings.setShouldShowSystemDecorsLocked(newDisp1, false);
        assertEquals(1, desktopModeEligibleChanged.size());
        assertEquals(newDisp1.mDisplayId, (int) desktopModeEligibleChanged.get(0));
        desktopModeEligibleChanged.clear();
    }

    @EnableFlags(FLAG_ENABLE_SYS_DECORS_CALLBACKS_VIA_WM)
    @Test
    public void testDisplayWindowListener_systemDecorations() {
        final ArrayList<Integer> displayAddSystemDecorations = new ArrayList<>();
        final ArrayList<Integer> displayRemoveSystemDecorations = new ArrayList<>();
        IDisplayWindowListener listener = new TestDisplayWindowListenerBase() {
            @Override
            public void onDisplayAddSystemDecorations(int displayId) {
                displayAddSystemDecorations.add(displayId);
            }

            @Override
            public void onDisplayRemoveSystemDecorations(int displayId) {
                displayRemoveSystemDecorations.add(displayId);
            }
        };
        mAtm.mWindowManager.registerDisplayWindowListener(listener);
        DisplayContent newDisp1 = new TestDisplayContent.Builder(mAtm, 600, 800).build();

        // Check adding decoration
        doReturn(true).when(newDisp1).isSystemDecorationsSupported();
        mAtm.mWindowManager.mDisplayWindowSettings.setShouldShowSystemDecorsLocked(newDisp1, true);
        assertEquals(1, displayAddSystemDecorations.size());
        assertEquals(newDisp1.mDisplayId, (int) displayAddSystemDecorations.get(0));
        displayAddSystemDecorations.clear();

        // Check removing decoration
        doReturn(false).when(newDisp1).isSystemDecorationsSupported();
        mAtm.mWindowManager.mDisplayWindowSettings.setShouldShowSystemDecorsLocked(newDisp1, false);
        assertEquals(1, displayRemoveSystemDecorations.size());
        assertEquals(newDisp1.mDisplayId, (int) displayRemoveSystemDecorations.get(0));
        displayRemoveSystemDecorations.clear();
    }

    @Test
    public void testSetLockScreenShownWithVirtualDisplay() {
        DisplayInfo displayInfo = new DisplayInfo();
        displayInfo.copyFrom(mDisplayInfo);
        displayInfo.type = Display.TYPE_VIRTUAL;
        DisplayContent virtualDisplay = createNewDisplay(displayInfo);
        final KeyguardController keyguardController = mSupervisor.getKeyguardController();

        // Make sure we're starting out with 2 unlocked displays
        assertEquals(2, mRootWindowContainer.getChildCount());
        mRootWindowContainer.forAllDisplays(displayContent -> {
            assertFalse(displayContent.isKeyguardLocked());
            assertFalse(keyguardController.isAodShowing(displayContent.mDisplayId));
        });

        // Check that setLockScreenShown locks both displays
        mAtm.setLockScreenShown(true, true);
        mRootWindowContainer.forAllDisplays(displayContent -> {
            assertTrue(displayContent.isKeyguardLocked());
            assertTrue(keyguardController.isAodShowing(displayContent.mDisplayId));
        });

        // Check setLockScreenShown unlocking both displays
        mAtm.setLockScreenShown(false, false);
        mRootWindowContainer.forAllDisplays(displayContent -> {
            assertFalse(displayContent.isKeyguardLocked());
            assertFalse(keyguardController.isAodShowing(displayContent.mDisplayId));
        });
    }

    @Test
    public void testSetLockScreenShownWithAlwaysUnlockedVirtualDisplay() {
        assertEquals(Display.DEFAULT_DISPLAY, mRootWindowContainer.getChildAt(0).getDisplayId());
        final KeyguardController keyguardController = mSupervisor.getKeyguardController();

        // The default display is locked before creating the virtual display
        mAtm.setLockScreenShown(true, true);

        // Create the virtual display
        DisplayInfo displayInfo = new DisplayInfo();
        displayInfo.copyFrom(mDisplayInfo);
        displayInfo.type = Display.TYPE_VIRTUAL;
        displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
        displayInfo.flags = Display.FLAG_OWN_DISPLAY_GROUP | Display.FLAG_ALWAYS_UNLOCKED;
        DisplayContent newDisplay = createNewDisplay(displayInfo);

        assertEquals(2, mRootWindowContainer.getChildCount());
        assertTrue(mDefaultDisplay.isKeyguardLocked());
        assertFalse(newDisplay.isKeyguardLocked());

        // Unlock the default display (this should have no effect for FLAG_ALWAYS_UNLOCKED)
        mAtm.keyguardGoingAway(0x0);
        mAtm.setLockScreenShown(false, false);

        // Make sure we now have both displays unlocked
        mRootWindowContainer.forAllDisplays(displayContent -> {
            assertFalse(displayContent.isKeyguardLocked());
            assertFalse(displayContent.isKeyguardGoingAway());
            assertFalse(keyguardController.isAodShowing(displayContent.mDisplayId));
        });

        // setLockScreenShown should only lock the default display, not the virtual one
        mAtm.setLockScreenShown(true, true);

        assertTrue(mDefaultDisplay.isKeyguardLocked());
        assertTrue(keyguardController.isAodShowing(mDefaultDisplay.mDisplayId));

        DisplayContent virtualDisplay = mRootWindowContainer.getDisplayContent(
                newDisplay.getDisplayId());
        assertNotEquals(Display.DEFAULT_DISPLAY, virtualDisplay.getDisplayId());
        assertFalse(virtualDisplay.isKeyguardLocked());
        assertFalse(keyguardController.isAodShowing(virtualDisplay.mDisplayId));
    }

    /*
        a test to verify b/144045134 - ignore PIP mode request for destroyed activity.
        mocks r.getParent() to return null to cause NPE inside enterPipRunnable#run() in
        ActivityTaskMangerservice#enterPictureInPictureMode(), which rebooted the device.
        It doesn't fully simulate the issue's reproduce steps, but this should suffice.
     */
    @Test
    public void testEnterPipModeWhenRecordParentChangesToNull() {
        final ActivityRecord record = new ActivityBuilder(mAtm).setCreateTask(true).build();
        PictureInPictureParams params = mock(PictureInPictureParams.class);
        record.pictureInPictureArgs = params;

        //mock operations in private method ensureValidPictureInPictureActivityParamsLocked()
        doReturn(true).when(record).supportsPictureInPicture();
        doReturn(false).when(params).hasSetAspectRatio();

        //mock other operations
        doReturn(true).when(record)
                .checkEnterPictureInPictureState("enterPictureInPictureMode", false);
        doReturn(false).when(record).inPinnedWindowingMode();
        doReturn(false).when(record).isKeyguardLocked();

        //to simulate NPE
        doReturn(null).when(record).getParent();

        mAtm.mActivityClientController.enterPictureInPictureMode(record.token, params);
        //if record's null parent is not handled gracefully, test will fail with NPE
    }

    @Test
    public void testEnterPipModeWhenResumed_autoEnterEnabled_returnTrue() {
        final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
        PictureInPictureParams params = mock(PictureInPictureParams.class);
        activity.pictureInPictureArgs = params;

        doReturn(true).when(activity).isState(RESUMED);
        doReturn(false).when(activity).inPinnedWindowingMode();
        doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
        doReturn(true).when(params).isAutoEnterEnabled();

        assertTrue(mAtm.enterPictureInPictureMode(activity, params,
                true /* fromClient */, true /* isAutoEnter */));
    }

    @Test
    public void testEnterPipModeWhenResumed_autoEnterDisabled_returnTrue() {
        final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
        PictureInPictureParams params = mock(PictureInPictureParams.class);
        activity.pictureInPictureArgs = params;

        doReturn(true).when(activity).isState(RESUMED);
        doReturn(false).when(activity).inPinnedWindowingMode();
        doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
        doReturn(false).when(params).isAutoEnterEnabled();

        assertTrue(mAtm.enterPictureInPictureMode(activity, params,
                true /* fromClient */, false /* isAutoEnter */));
    }

    @Test
    public void testEnterPipModeWhenPausing_autoEnterEnabled_returnFalse() {
        final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
        PictureInPictureParams params = mock(PictureInPictureParams.class);
        activity.pictureInPictureArgs = params;

        doReturn(true).when(activity).isState(PAUSING);
        doReturn(false).when(activity).inPinnedWindowingMode();
        doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
        doReturn(true).when(params).isAutoEnterEnabled();

        assertFalse(mAtm.enterPictureInPictureMode(activity, params,
                true /* fromClient */, true /* isAutoEnter */));
    }

    @Test
    public void testEnterPipModeWhenPausing_autoEnterDisabled_returnTrue() {
        final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
        PictureInPictureParams params = mock(PictureInPictureParams.class);
        activity.pictureInPictureArgs = params;

        doReturn(true).when(activity).isState(PAUSING);
        doReturn(false).when(activity).inPinnedWindowingMode();
        doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
        doReturn(false).when(params).isAutoEnterEnabled();

        assertTrue(mAtm.enterPictureInPictureMode(activity, params,
                true /* fromClient */, false /* isAutoEnter */));
    }

    @Test
    public void testResumeNextActivityOnCrashedAppDied() {
        final ActivityRecord homeActivity = new ActivityBuilder(mAtm)
                .setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask())
                .build();
        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
        activity.setState(RESUMED, "test");

        assertEquals(activity.app, mAtm.mInternal.getTopApp());

        // Assume the activity is finishing and hidden because it was crashed.
        activity.finishing = true;
        activity.setVisibleRequested(false);
        activity.setVisible(false);
        activity.getTask().setPausingActivity(activity);
        homeActivity.setState(PAUSED, "test");

        // Even the visibility states are invisible, the next activity should be resumed because
        // the crashed activity was pausing.
        mAtm.mInternal.handleAppDied(activity.app, false /* restarting */,
                null /* finishInstrumentationCallback */);
        assertEquals(RESUMED, homeActivity.getState());
        assertEquals(homeActivity.app, mAtm.mInternal.getTopApp());
    }

    @Test
    public void testUpdateSleep() {
        doCallRealMethod().when(mWm.mRoot).hasAwakeDisplay();
        mSupervisor.mGoingToSleepWakeLock =
                mSystemServicesTestRule.createStubbedWakeLock(true /* needVerification */);
        final Task rootHomeTask = mWm.mRoot.getDefaultTaskDisplayArea().getOrCreateRootHomeTask();
        final ActivityRecord homeActivity = new ActivityBuilder(mAtm).setTask(rootHomeTask).build();
        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
        topActivity.setState(RESUMED, "test");

        final Consumer<ActivityRecord> assertTopNonSleeping = activity -> {
            assertFalse(mAtm.mInternal.isSleeping());
            assertEquals(ActivityManager.PROCESS_STATE_TOP, mAtm.mInternal.getTopProcessState());
            assertEquals(activity.app, mAtm.mInternal.getTopApp());
        };
        assertTopNonSleeping.accept(topActivity);

        // Sleep all displays.
        mWm.mRoot.forAllDisplays(display -> doReturn(true).when(display).shouldSleep());
        mAtm.updateSleepIfNeededLocked();
        // Simulate holding sleep wake lock if it is acquired.
        verify(mSupervisor.mGoingToSleepWakeLock).acquire();
        doReturn(true).when(mSupervisor.mGoingToSleepWakeLock).isHeld();

        assertEquals(PAUSING, topActivity.getState());
        assertTrue(mAtm.mInternal.isSleeping());
        assertEquals(ActivityManager.PROCESS_STATE_TOP_SLEEPING,
                mAtm.mInternal.getTopProcessState());
        // The top app should not change while sleeping.
        assertEquals(topActivity.app, mAtm.mInternal.getTopApp());

        mAtm.startPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY
                | ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY);
        assertEquals(ActivityManager.PROCESS_STATE_TOP, mAtm.mInternal.getTopProcessState());
        // Because there is no unknown visibility record, the state will be restored if other
        // reasons are all done.
        mAtm.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
        assertEquals(ActivityManager.PROCESS_STATE_TOP_SLEEPING,
                mAtm.mInternal.getTopProcessState());

        // If all activities are stopped, the sleep wake lock must be released.
        final Task topRootTask = topActivity.getRootTask();
        doReturn(true).when(rootHomeTask).goToSleepIfPossible(anyBoolean());
        doReturn(true).when(topRootTask).goToSleepIfPossible(anyBoolean());
        topActivity.setState(STOPPING, "test");
        topActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
                null /* handoffActivityData */, null /* description */);
        verify(mSupervisor.mGoingToSleepWakeLock).release();

        // Move the current top to back, the top app should update to the next activity.
        topRootTask.moveToBack("test", null /* self */);
        assertEquals(homeActivity.app, mAtm.mInternal.getTopApp());

        // Wake all displays.
        mWm.mRoot.forAllDisplays(display -> doReturn(false).when(display).shouldSleep());
        mAtm.updateSleepIfNeededLocked();

        assertTopNonSleeping.accept(homeActivity);
    }

    @Test
    public void testSetPowerMode() {
        // Depends on the mocked power manager set in SystemServicesTestRule#setUpLocalServices.
        mAtm.onInitPowerManagement();

        // Apply different power modes according to the reasons.
        mAtm.startPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
        verify(mWm.mPowerManagerInternal).setPowerMode(
                PowerManagerInternal.MODE_LAUNCH, true);
        mAtm.startPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY);
        verify(mWm.mPowerManagerInternal).setPowerMode(
                PowerManagerInternal.MODE_DISPLAY_CHANGE, true);

        // If there is unknown visibility launching app, the launch power mode won't be canceled
        // even if REASON_START_ACTIVITY is cleared.
        mAtm.startPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY);
        mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(mock(ActivityRecord.class));
        mAtm.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
        verify(mWm.mPowerManagerInternal, never()).setPowerMode(
                PowerManagerInternal.MODE_LAUNCH, false);

        mDisplayContent.mUnknownAppVisibilityController.clear();
        mAtm.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
        verify(mWm.mPowerManagerInternal).setPowerMode(
                PowerManagerInternal.MODE_LAUNCH, false);

        mAtm.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY);
        verify(mWm.mPowerManagerInternal).setPowerMode(
                PowerManagerInternal.MODE_DISPLAY_CHANGE, false);
    }

    @Test
    public void testUpdatePreviousProcess() {
        final ActivityRecord activity = new ActivityBuilder(mAtm).build();
        activity.lastVisibleTime = 1;
        mAtm.mTopApp = mock(WindowProcessController.class);
        mAtm.updatePreviousProcess(activity);

        assertEquals(activity.app, mAtm.mPreviousProcess);

        final ActivityRecord recentsActivity = new ActivityBuilder(mAtm)
                .setUid("recents".hashCode()).build();
        final RecentTasks recentTasks = mAtm.getRecentTasks();
        spyOn(recentTasks);
        doReturn(true).when(recentTasks).isRecentsComponent(
                eq(recentsActivity.mActivityComponent),
                eq(recentsActivity.info.applicationInfo.uid));
        recentsActivity.lastVisibleTime = 2;
        mAtm.updatePreviousProcess(recentsActivity);

        assertEquals("RecentsActivity must not occupy 'previous process'",
                activity.app, mAtm.mPreviousProcess);
    }

    @Test
    public void testSupportsMultiWindow_resizable() {
        final ActivityRecord activity = new ActivityBuilder(mAtm)
                .setCreateTask(true)
                .setResizeMode(RESIZE_MODE_RESIZEABLE)
                .build();
        final Task task = activity.getTask();

        assertTrue(activity.supportsMultiWindow());
        assertTrue(task.supportsMultiWindow());
    }

    @Test
    public void testSupportsMultiWindow_nonResizable() {
        final ActivityRecord activity = new ActivityBuilder(mAtm)
                .setComponent(getUniqueComponentName(mContext.getPackageName()))
                .setCreateTask(true)
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .build();
        final Task task = activity.getTask();
        final TaskDisplayArea tda = task.getDisplayArea();

        // Device config as not support.
        mAtm.mSupportsNonResizableMultiWindow = -1;

        assertFalse(activity.supportsMultiWindow());
        assertFalse(task.supportsMultiWindow());

        // Device config as always support.
        mAtm.mSupportsNonResizableMultiWindow = 1;

        assertTrue(activity.supportsMultiWindow());
        assertTrue(task.supportsMultiWindow());

        // The default config is relying on the screen size.
        mAtm.mSupportsNonResizableMultiWindow = 0;

        // Supports on large screen.
        tda.getConfiguration().smallestScreenWidthDp =
                WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;

        assertTrue(activity.supportsMultiWindow());
        assertTrue(task.supportsMultiWindow());

        // Not supports on small screen.
        tda.getConfiguration().smallestScreenWidthDp =
                WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP - 1;

        assertFalse(activity.supportsMultiWindow());
        assertFalse(task.supportsMultiWindow());
    }

    @Test
    public void testSupportsMultiWindow_activityMinWidthHeight_largerThanSupport() {
        final float density = mContext.getResources().getDisplayMetrics().density;
        final ActivityInfo.WindowLayout windowLayout =
                new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
                        // This is larger than the min dimensions device support in multi window,
                        // the activity will not be supported in multi window if the device respects
                        /* minWidth= */
                        (int) (WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP * density),
                        /* minHeight= */
                        (int) (WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP * density));
        final ActivityRecord activity = new ActivityBuilder(mAtm)
                .setCreateTask(true)
                .setWindowLayout(windowLayout)
                .setResizeMode(RESIZE_MODE_RESIZEABLE)
                .build();
        final Task task = activity.getTask();
        final TaskDisplayArea tda = task.getDisplayArea();
        // Ensure the display is not a large screen
        if (tda.getConfiguration().smallestScreenWidthDp
                >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) {
            resizeDisplay(activity.mDisplayContent, 500, 800);
        }

        // Ignore the activity min width/height for determine multi window eligibility.
        mAtm.mRespectsActivityMinWidthHeightMultiWindow = -1;

        assertTrue(activity.supportsMultiWindow());
        assertTrue(task.supportsMultiWindow());

        // Always check the activity min width/height.
        mAtm.mRespectsActivityMinWidthHeightMultiWindow = 1;

        assertFalse(activity.supportsMultiWindow());
        assertFalse(task.supportsMultiWindow());

        // The default config is relying on the screen size.
        mAtm.mRespectsActivityMinWidthHeightMultiWindow = 0;

        // Ignore on large screen.
        tda.getConfiguration().smallestScreenWidthDp =
                WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;

        assertTrue(activity.supportsMultiWindow());
        assertTrue(task.supportsMultiWindow());

        // Check on small screen.
        tda.getConfiguration().smallestScreenWidthDp =
                WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP - 1;

        assertFalse(activity.supportsMultiWindow());
        assertFalse(task.supportsMultiWindow());
    }

    @Test
    public void testSupportsMultiWindow_landscape_checkActivityMinWidth() {
        // This is smaller than the min dimensions device support in multi window,
        // the activity will be supported in multi window
        final float density = mContext.getResources().getDisplayMetrics().density;
        final int supportedWidth = (int) (WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
                * mAtm.mMinPercentageMultiWindowSupportWidth * density);
        final ActivityInfo.WindowLayout windowLayout =
                new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
                        /* minWidth= */ supportedWidth,
                        /* minHeight= */ 0);
        final ActivityRecord activity = new ActivityBuilder(mAtm)
                .setCreateTask(true)
                .setWindowLayout(windowLayout)
                .setResizeMode(RESIZE_MODE_RESIZEABLE)
                .build();
        final Task task = activity.getTask();
        final TaskDisplayArea tda = task.getDisplayArea();
        tda.getConfiguration().smallestScreenWidthDp =
                WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP - 1;
        tda.getConfiguration().screenWidthDp =
                WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP - 1;
        tda.getConfiguration().orientation = ORIENTATION_LANDSCAPE;

        assertFalse(activity.supportsMultiWindow());
        assertFalse(task.supportsMultiWindow());

        tda.getConfiguration().screenWidthDp = (int) Math.ceil(
                WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
                        / mAtm.mMinPercentageMultiWindowSupportWidth);

        assertTrue(activity.supportsMultiWindow());
        assertTrue(task.supportsMultiWindow());
    }

    @Test
    public void testSupportsMultiWindow_portrait_checkActivityMinHeight() {
        // This is smaller than the min dimensions device support in multi window,
        // the activity will be supported in multi window
        final float density = mContext.getResources().getDisplayMetrics().density;
        final int supportedHeight = (int) (WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
                * mAtm.mMinPercentageMultiWindowSupportHeight * density);
        final ActivityInfo.WindowLayout windowLayout =
                new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
                        /* minWidth= */ 0,
                        /* minHeight= */ supportedHeight);
        final ActivityRecord activity = new ActivityBuilder(mAtm)
                .setCreateTask(true)
                .setWindowLayout(windowLayout)
                .setResizeMode(RESIZE_MODE_RESIZEABLE)
                .build();
        final Task task = activity.getTask();
        final TaskDisplayArea tda = task.getDisplayArea();
        tda.getConfiguration().smallestScreenWidthDp =
                WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP - 1;
        tda.getConfiguration().screenHeightDp =
                WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP - 1;
        tda.getConfiguration().orientation = ORIENTATION_PORTRAIT;

        assertFalse(activity.supportsMultiWindow());
        assertFalse(task.supportsMultiWindow());

        tda.getConfiguration().screenHeightDp = (int) Math.ceil(
                WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
                        / mAtm.mMinPercentageMultiWindowSupportHeight);

        assertTrue(activity.supportsMultiWindow());
        assertTrue(task.supportsMultiWindow());
    }

    @Test
    public void testPackageConfigUpdate_locales_successfullyApplied() {
        registerTestTransitionPlayer();
        addNewDisplayContentAt(DisplayContent.POSITION_TOP);
        final DisplayContent dc0 = mRootWindowContainer.getChildAt(0);
        final DisplayContent dc1 = mRootWindowContainer.getChildAt(1);
        dc0.setLastHasContent();
        dc1.setLastHasContent();
        final Configuration config = new Configuration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateConfigurationLocked(config, true /* initLocale */, true /* persistent */,
                DEFAULT_USER_ID);

        assertTrue(dc0.inTransition());
        assertTrue(dc1.inTransition());

        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));

        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater();
        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit();

        WindowProcessController wpcAfterConfigChange = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);

        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpcAfterConfigChange.getConfiguration().getLocales());
        assertFalse(wpcAfterConfigChange.getConfiguration().isNightModeActive());
    }

    @Test
    public void testPackageConfigUpdate_nightMode_successfullyApplied() {
        Configuration config = mAtm.getGlobalConfiguration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater();

        packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();

        WindowProcessController wpcAfterConfigChange = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertTrue(wpcAfterConfigChange.getConfiguration().isNightModeActive());
        assertEquals(LocaleList.forLanguageTags("en-XC"),
                wpcAfterConfigChange.getConfiguration().getLocales());
    }

    @Test
    public void testPackageConfigUpdate_multipleLocaleUpdates_successfullyApplied() {
        Configuration config = mAtm.getGlobalConfiguration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
        WindowProcessController wpc = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater();

        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();

        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpcAfterConfigChange1.getConfiguration().getLocales());
        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpc.getConfiguration().getLocales());

        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("ja-XC,en-XC")).commit();

        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);

        assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"),
                wpcAfterConfigChange2.getConfiguration().getLocales());
        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
        assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"),
                wpc.getConfiguration().getLocales());
    }

    @Test
    public void testPackageConfigUpdate_multipleNightModeUpdates_successfullyApplied() {
        Configuration config = mAtm.getGlobalConfiguration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater();

        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();

        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpcAfterConfigChange1.getConfiguration().getLocales());
        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());

        packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_NO).commit();

        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpcAfterConfigChange2.getConfiguration().getLocales());
        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
    }

    @Test
    public void testPackageConfigUpdate_onPackageUninstall_configShouldNotApply() {
        Configuration config = mAtm.getGlobalConfiguration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater();

        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();

        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpcAfterConfigChange1.getConfiguration().getLocales());
        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());

        mAtm.mInternal.onPackageUninstalled(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);

        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XC"),
                wpcAfterConfigChange2.getConfiguration().getLocales());
        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
    }

    @Test
    public void testPackageConfigUpdate_LocalesEmptyAndNightModeUndefined_configShouldNotApply() {
        Configuration config = mAtm.getGlobalConfiguration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
        WindowProcessController wpc = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater();

        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);

        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpcAfterConfigChange1.getConfiguration().getLocales());
        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpc.getConfiguration().getLocales());

        packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList())
                .setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit();

        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XC"),
                wpcAfterConfigChange2.getConfiguration().getLocales());
        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
        assertEquals(LocaleList.forLanguageTags("en-XC"),
                wpc.getConfiguration().getLocales());
    }

    @Test
    public void testPackageConfigUpdate_WhenUserRemoved_configShouldNotApply() {
        Configuration config = mAtm.getGlobalConfiguration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));

        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater();

        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();

        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpcAfterConfigChange1.getConfiguration().getLocales());
        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());

        mAtm.mInternal.removeUser(DEFAULT_USER_ID);

        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XC"),
                wpcAfterConfigChange2.getConfiguration().getLocales());
        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
    }

    @Test
    public void testPackageConfigUpdate_setLocaleListToEmpty_doesNotOverlayLocaleListInWpc() {
        Configuration config = mAtm.getGlobalConfiguration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));

        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater();

        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();

        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpcAfterConfigChange1.getConfiguration().getLocales());
        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());

        packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit();

        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XC"),
                wpcAfterConfigChange2.getConfiguration().getLocales());
        assertTrue(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
    }

    @Test
    public void testPackageConfigUpdate_resetNightMode_doesNotOverrideNightModeInWpc() {
        Configuration config = mAtm.getGlobalConfiguration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));

        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater();

        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();

        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpcAfterConfigChange1.getConfiguration().getLocales());
        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());

        packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit();

        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpcAfterConfigChange2.getConfiguration().getLocales());
        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
    }

    @Test
    public void testPackageConfigUpdate_localesNotSet_localeConfigRetrievedNull() {
        Configuration config = mAtm.getGlobalConfiguration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateGlobalConfigurationLocked(config, true, true,
                DEFAULT_USER_ID);
        WindowProcessController wpc = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);

        ActivityTaskManagerInternal.PackageConfig appSpecificConfig = mAtm.mInternal
                .getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        // when no configuration is set we get a null object.
        assertNull(appSpecificConfig);

        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
                        DEFAULT_USER_ID);
        packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();

        ActivityTaskManagerInternal.PackageConfig appSpecificConfig2 = mAtm.mInternal
                .getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertNotNull(appSpecificConfig2);
        assertNull(appSpecificConfig2.mLocales);
        assertEquals(appSpecificConfig2.mNightMode.intValue(), Configuration.UI_MODE_NIGHT_YES);
    }

    @Test
    public void testPackageConfigUpdate_appNotRunning_configSuccessfullyApplied() {
        Configuration config = mAtm.getGlobalConfiguration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateGlobalConfigurationLocked(config, true, true,
                DEFAULT_USER_ID);

        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
                        DEFAULT_USER_ID);
        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit();

        // Verifies if the persisted app-specific configuration is same as the committed
        // configuration.
        ActivityTaskManagerInternal.PackageConfig appSpecificConfig = mAtm.mInternal
                .getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertNotNull(appSpecificConfig);
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB"), appSpecificConfig.mLocales);

        // Verifies if the persisted configuration for an arbitrary app is applied correctly when
        // a new WindowProcessController is created for it.
        WindowProcessController wpcAfterConfigChange = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpcAfterConfigChange.getConfiguration().getLocales());
    }

    @Test
    public void testPackageConfigUpdate_appRunning_configSuccessfullyApplied() {
        Configuration config = mAtm.getGlobalConfiguration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateGlobalConfigurationLocked(config, true, true,
                DEFAULT_USER_ID);
        WindowProcessController wpc = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);

        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
                        DEFAULT_USER_ID);

        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit();

        ActivityTaskManagerInternal.PackageConfig appSpecificConfig = mAtm.mInternal
                .getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);

        // Verifies if the persisted app-specific configuration is same as the committed
        // configuration.
        assertNotNull(appSpecificConfig);
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB"), appSpecificConfig.mLocales);

        // Verifies if the committed configuration is successfully applied to the required
        // application while it is currently running.
        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
                wpc.getConfiguration().getLocales());
    }

    @Test
    public void testPackageConfigUpdate_commitConfig_configSuccessfullyApplied() {
        Configuration config = mAtm.getGlobalConfiguration();
        config.setLocales(LocaleList.forLanguageTags("en-XC"));
        mAtm.updateGlobalConfigurationLocked(config, true, true,
                DEFAULT_USER_ID);
        WindowProcessController wpc = createWindowProcessController(
                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
        mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);

        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
                        DEFAULT_USER_ID);

        // committing empty locales, when no config is set should return false.
        assertFalse(packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit());

        // committing new configuration returns true;
        assertTrue(packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                .commit());
        // applying the same configuration returns false.
        assertFalse(packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                .commit());

        // committing empty locales and undefined nightMode should return true (deletes the
        // pre-existing record) if some config was previously set.
        assertTrue(packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList())
                .setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit());
    }

    private WindowProcessController createWindowProcessController(String packageName,
            int userId) {
        WindowProcessListener mMockListener = Mockito.mock(WindowProcessListener.class);
        ApplicationInfo info = mock(ApplicationInfo.class);
        info.packageName = packageName;
        WindowProcessController wpc = new WindowProcessController(
                mAtm, info, packageName, 0, userId, null, mMockListener);
        mAtm.mInternal.preBindApplication(wpc, info);
        mAtm.mInternal.onProcessAdded(wpc);
        wpc.setThread(mock(IApplicationThread.class));
        return wpc;
    }

    @Test(expected = IllegalArgumentException.class)
    public void testRegisterActivityStartInterceptor_IndexTooSmall() {
        mAtm.mInternal.registerActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID - 1,
                new ActivityInterceptorCallback() {
                    @Nullable
                    @Override
                    public ActivityInterceptResult onInterceptActivityLaunch(
                            @NonNull ActivityInterceptorInfo info) {
                        return null;
                    }
                });
    }

    @Test(expected = IllegalArgumentException.class)
    public void testRegisterActivityStartInterceptor_IndexTooLarge() {
        mAtm.mInternal.registerActivityStartInterceptor(SYSTEM_LAST_ORDERED_ID + 1,
                new ActivityInterceptorCallback() {
                    @Nullable
                    @Override
                    public ActivityInterceptResult onInterceptActivityLaunch(
                            @NonNull ActivityInterceptorInfo info) {
                        return null;
                    }
                });
    }

    @Test(expected = IllegalArgumentException.class)
    public void testRegisterActivityStartInterceptor_DuplicateId() {
        mAtm.mInternal.registerActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID,
                new ActivityInterceptorCallback() {
                    @Nullable
                    @Override
                    public ActivityInterceptResult onInterceptActivityLaunch(
                            @NonNull ActivityInterceptorInfo info) {
                        return null;
                    }
                });
        mAtm.mInternal.registerActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID,
                new ActivityInterceptorCallback() {
                    @Nullable
                    @Override
                    public ActivityInterceptResult onInterceptActivityLaunch(
                            @NonNull ActivityInterceptorInfo info) {
                        return null;
                    }
                });
    }

    @Test
    public void testRegisterActivityStartInterceptor() {
        assertEquals(0, mAtm.getActivityInterceptorCallbacks().size());

        mAtm.mInternal.registerActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID,
                new ActivityInterceptorCallback() {
                    @Nullable
                    @Override
                    public ActivityInterceptResult onInterceptActivityLaunch(
                            @NonNull ActivityInterceptorInfo info) {
                        return null;
                    }
                });

        assertEquals(1, mAtm.getActivityInterceptorCallbacks().size());
        assertTrue(mAtm.getActivityInterceptorCallbacks().contains(SYSTEM_FIRST_ORDERED_ID));
    }

    @Test
    public void testSystemAndMainlineOrderIdsNotOverlapping() {
        assertTrue(MAINLINE_FIRST_ORDERED_ID - SYSTEM_LAST_ORDERED_ID > 1);
    }

    @Test
    public void testUnregisterActivityStartInterceptor() {
        int size = mAtm.getActivityInterceptorCallbacks().size();
        int orderId = SYSTEM_FIRST_ORDERED_ID;

        mAtm.mInternal.registerActivityStartInterceptor(orderId,
                (ActivityInterceptorCallback) info -> null);
        assertEquals(size + 1, mAtm.getActivityInterceptorCallbacks().size());
        assertTrue(mAtm.getActivityInterceptorCallbacks().contains(orderId));

        mAtm.mInternal.unregisterActivityStartInterceptor(orderId);
        assertEquals(size, mAtm.getActivityInterceptorCallbacks().size());
        assertFalse(mAtm.getActivityInterceptorCallbacks().contains(orderId));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testUnregisterActivityStartInterceptor_IdNotExist() {
        assertEquals(0, mAtm.getActivityInterceptorCallbacks().size());
        mAtm.mInternal.unregisterActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID);
    }

    @Test
    public void testFocusTopTask() {
        final ActivityRecord homeActivity = new ActivityBuilder(mAtm)
                .setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask())
                .build();
        final Task pinnedTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
                .setWindowingMode(WINDOWING_MODE_PINNED)
                .build();
        mAtm.focusTopTask(mDisplayContent.mDisplayId);

        assertTrue(homeActivity.getTask().isFocused());
        assertFalse(pinnedTask.isFocused());
    }

    @Test
    public void testContinueWindowLayout_notifyClientLifecycleManager() {
        clearInvocations(mClientLifecycleManager);
        mAtm.deferWindowLayout();

        verify(mClientLifecycleManager, never()).onLayoutContinued();

        mAtm.continueWindowLayout();

        verify(mClientLifecycleManager).onLayoutContinued();
    }

    @Test
    public void testGetTaskDescriptionIcon_matchingUid() {
        // Ensure that we do not hold MANAGE_ACTIVITY_TASKS
        doThrow(new SecurityException()).when(mAtm).enforceActivityTaskPermission(any());

        final String filePath = "abc/def";
        // Create an activity with a task description at the test icon filepath
        final ActivityRecord activity = new ActivityBuilder(mAtm)
                .setUid(android.os.Process.myUid())
                .setCreateTask(true)
                .build();
        final TaskDescription td = new TaskDescription.Builder().build();
        td.setIconFilename(filePath);
        activity.setTaskDescription(td);

        // Verify this calls and does not throw a security exception
        try {
            mAtm.getTaskDescriptionIcon(filePath, activity.mUserId);
        } catch (SecurityException e) {
            fail("Unexpected security exception: " + e);
        } catch (IllegalArgumentException e) {
            // Ok, the file doesn't actually exist
        }
    }

    @Test
    public void testGetTaskDescriptionIcon_noMatchingActivity_expectException() {
        // Ensure that we do not hold MANAGE_ACTIVITY_TASKS
        doThrow(new SecurityException()).when(mAtm).enforceActivityTaskPermission(any());

        final String filePath = "abc/def";

        // Verify this throws a security exception due to no matching activity
        assertThrows(SecurityException.class,
                () -> mAtm.getTaskDescriptionIcon(filePath, UserHandle.myUserId()));
    }

    @Test
    public void testGetTaskDescriptionIcon_noMatchingUid_expectException() {
        // Ensure that we do not hold MANAGE_ACTIVITY_TASKS
        doThrow(new SecurityException()).when(mAtm).enforceActivityTaskPermission(any());

        final String filePath = "abc/def";
        // Create an activity with a task description at the test icon filepath
        final ActivityRecord activity = new ActivityBuilder(mAtm)
                .setCreateTask(true)
                .setUid(101010)
                .build();
        final TaskDescription td = new TaskDescription.Builder().build();
        td.setIconFilename(filePath);
        activity.setTaskDescription(td);

        // Verify this throws a security exception due to no matching UID
        assertThrows(SecurityException.class,
                () -> mAtm.getTaskDescriptionIcon(filePath, activity.mUserId));
    }

    @Test
    public void testRequestEnterPipModeWhenTaskIsDisabledPip_notEnterPip() {
        final ActivityRecord record = new ActivityBuilder(mAtm).setCreateTask(true).build();
        PictureInPictureParams params = mock(PictureInPictureParams.class);
        record.pictureInPictureArgs = params;

        //mock operations in private method ensureValidPictureInPictureActivityParamsLocked()
        doReturn(true).when(record).supportsPictureInPicture();
        doReturn(false).when(params).hasSetAspectRatio();

        record.getTask().setDisablePip(true);

        assertFalse(mAtm.mActivityClientController.enterPictureInPictureMode(record.token, params));
        assertFalse(record.inPinnedWindowingMode());
    }

    @EnableFlags(FLAG_ENABLE_WINDOW_REPOSITIONING_API)
    @Test
    public void testIsTaskMoveAllowedOnDisplay_permissionGranted() {
        final int displayId = Display.DEFAULT_DISPLAY;
        final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);

        MockitoSession session =
                mockitoSession().spyStatic(ActivityTaskManagerService.class).startMocking();
        try {
            doReturn(PERMISSION_GRANTED).when(() -> {
                return ActivityTaskManagerService.checkPermission(
                        eq(REPOSITION_SELF_WINDOWS), anyInt(), anyInt());
            });

            doReturn(true).when(dc).isTaskMoveAllowedOnDisplay();
            assertTrue(mAtm.isTaskMoveAllowedOnDisplay(displayId));

            doReturn(false).when(dc).isTaskMoveAllowedOnDisplay();
            assertFalse(mAtm.isTaskMoveAllowedOnDisplay(displayId));
        } finally {
            session.finishMocking();
        }
    }

    @EnableFlags(FLAG_ENABLE_WINDOW_REPOSITIONING_API)
    @Test
    public void testIsTaskMoveAllowedOnDisplay_permissionDenied() {
        final int displayId = Display.DEFAULT_DISPLAY;
        final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);

        MockitoSession session =
                mockitoSession().spyStatic(ActivityTaskManagerService.class).startMocking();
        try {
            doReturn(PERMISSION_DENIED).when(() -> {
                return ActivityTaskManagerService.checkPermission(
                        eq(REPOSITION_SELF_WINDOWS), anyInt(), anyInt());
            });

            doReturn(true).when(dc).isTaskMoveAllowedOnDisplay();
            assertFalse(mAtm.isTaskMoveAllowedOnDisplay(displayId));

            doReturn(false).when(dc).isTaskMoveAllowedOnDisplay();
            assertFalse(mAtm.isTaskMoveAllowedOnDisplay(displayId));
        } finally {
            session.finishMocking();
        }
    }
}
