Revert "DO NOT MERGE: Move AM/WM unit tests out of FrameworksServicesTests to WmTests"

This reverts commit dee5a4dc2a5a6829e5509fc0e94903e30dcc238f.

Reason for revert: CLs on upstream branch are on hold.

Change-Id: I253294d080efdfd1dbf377812f12880e03dff1f6
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java
new file mode 100644
index 0000000..f741c70
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2017 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 android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.am.LaunchParamsController.LaunchParams;
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.doAnswer;
+
+import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
+
+/**
+ * Tests for exercising resizing bounds due to activity options.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:ActivityLaunchParamsModifierTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ActivityLaunchParamsModifierTests extends ActivityTestsBase {
+    private ActivityLaunchParamsModifier mModifier;
+    private ActivityManagerService mService;
+    private ActivityStack mStack;
+    private TaskRecord mTask;
+    private ActivityRecord mActivity;
+
+    private LaunchParams mCurrent;
+    private LaunchParams mResult;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mService = createActivityManagerService();
+        mModifier = new ActivityLaunchParamsModifier(mService.mStackSupervisor);
+        mCurrent = new LaunchParams();
+        mResult = new LaunchParams();
+
+
+        mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+        mActivity = new ActivityBuilder(mService).setTask(mTask).build();
+    }
+
+
+    @Test
+    public void testSkippedInvocations() throws Exception {
+        // No specified activity should be ignored
+        assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
+                null /*activity*/, null /*source*/, null /*options*/, mCurrent, mResult));
+
+        // No specified activity options should be ignored
+        assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, null /*options*/, mCurrent, mResult));
+
+        // launch bounds specified should be ignored.
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+        // Non-resizeable records should be ignored
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+        assertFalse(mActivity.isResizeable());
+        assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+        // make record resizeable
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+        assertTrue(mActivity.isResizeable());
+
+        assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+        // Does not support freeform
+        mService.mSupportsFreeformWindowManagement = false;
+        assertFalse(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options));
+        assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+        mService.mSupportsFreeformWindowManagement = true;
+        options.setLaunchBounds(new Rect());
+        assertTrue(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options));
+
+        // Invalid bounds
+        assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+        options.setLaunchBounds(new Rect(0, 0, -1, -1));
+        assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+        // Valid bounds should cause the positioner to be applied.
+        options.setLaunchBounds(new Rect(0, 0, 100, 100));
+        assertEquals(RESULT_DONE, mModifier.onCalculate(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+    }
+
+    @Test
+    public void testBoundsExtraction() throws Exception {
+        // Make activity resizeable and enable freeform mode.
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+        mService.mSupportsFreeformWindowManagement = true;
+
+        ActivityOptions options = ActivityOptions.makeBasic();
+        final Rect proposedBounds = new Rect(20, 30, 45, 40);
+        options.setLaunchBounds(proposedBounds);
+
+        assertEquals(RESULT_DONE, mModifier.onCalculate(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+        assertEquals(mResult.mBounds, proposedBounds);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
new file mode 100644
index 0000000..bce87dc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2017 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManagerInternal;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link ActivityManagerInternal}.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.server.am.ActivityManagerInternalTest frameworks-services
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksServicesTests
+ * Install: adb install -r \
+ *     ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -e class com.android.server.am.ActivityManagerInternalTest -w \
+ *     com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerInternalTest {
+    private static final int TEST_UID1 = 111;
+    private static final int TEST_UID2 = 112;
+
+    private static final long TEST_PROC_STATE_SEQ1 = 1111;
+    private static final long TEST_PROC_STATE_SEQ2 = 1112;
+    private static final long TEST_PROC_STATE_SEQ3 = 1113;
+
+    @Mock private ActivityManagerService.Injector mMockInjector;
+
+    private ActivityManagerService mAms;
+    private ActivityManagerInternal mAmi;
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mAms = new ActivityManagerService(mMockInjector);
+        mAmi = mAms.new LocalService();
+    }
+
+    @MediumTest
+    @Test
+    public void testNotifyNetworkPolicyRulesUpdated() throws Exception {
+        // Check there is no crash when there are no active uid records.
+        mAmi.notifyNetworkPolicyRulesUpdated(TEST_UID1, TEST_PROC_STATE_SEQ1);
+
+        // Notify that network policy rules are updated for TEST_UID1 and verify that
+        // UidRecord.lastNetworkUpdateProcStateSeq is updated and any blocked threads are notified.
+        verifyNetworkUpdatedProcStateSeq(
+                TEST_PROC_STATE_SEQ2, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
+                TEST_PROC_STATE_SEQ2, // procStateSeq to notify
+                true); // expectNotify
+
+        // Notify that network policy rules are updated for TEST_UID1 with already handled
+        // procStateSeq and verify that there is no notify call.
+        verifyNetworkUpdatedProcStateSeq(
+                TEST_PROC_STATE_SEQ1, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
+                TEST_PROC_STATE_SEQ1, // procStateSeq to notify
+                false); // expectNotify
+
+        // Notify that network policy rules are updated for TEST_UID1 with procStateSeq older
+        // than it's UidRecord.curProcStateSeq and verify that there is no notify call.
+        verifyNetworkUpdatedProcStateSeq(
+                TEST_PROC_STATE_SEQ3, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
+                TEST_PROC_STATE_SEQ2, // procStateSeq to notify
+                false); // expectNotify
+    }
+
+    private void verifyNetworkUpdatedProcStateSeq(long curProcStateSeq,
+            long lastNetworkUpdatedProcStateSeq, long expectedProcStateSeq, boolean expectNotify)
+            throws Exception {
+        final UidRecord record1 = addActiveUidRecord(TEST_UID1, curProcStateSeq,
+                lastNetworkUpdatedProcStateSeq);
+        final UidRecord record2 = addActiveUidRecord(TEST_UID2, curProcStateSeq,
+                lastNetworkUpdatedProcStateSeq);
+
+        final CustomThread thread1 = new CustomThread(record1.networkStateLock);
+        thread1.startAndWait("Unexpected state for " + record1);
+        final CustomThread thread2 = new CustomThread(record2.networkStateLock);
+        thread2.startAndWait("Unexpected state for " + record2);
+
+        mAmi.notifyNetworkPolicyRulesUpdated(TEST_UID1, expectedProcStateSeq);
+        assertEquals(record1 + " should be updated",
+                expectedProcStateSeq, record1.lastNetworkUpdatedProcStateSeq);
+        assertEquals(record2 + " should not be updated",
+                lastNetworkUpdatedProcStateSeq, record2.lastNetworkUpdatedProcStateSeq);
+
+        if (expectNotify) {
+            thread1.assertTerminated("Unexpected state for " + record1);
+            assertTrue("Threads waiting for network should be notified: " + record1,
+                    thread1.mNotified);
+        } else {
+            thread1.assertWaiting("Unexpected state for " + record1);
+            thread1.interrupt();
+        }
+        thread2.assertWaiting("Unexpected state for " + record2);
+        thread2.interrupt();
+
+        mAms.mActiveUids.clear();
+    }
+
+    private UidRecord addActiveUidRecord(int uid, long curProcStateSeq,
+            long lastNetworkUpdatedProcStateSeq) {
+        final UidRecord record = new UidRecord(uid);
+        record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
+        record.curProcStateSeq = curProcStateSeq;
+        record.waitingForNetwork = true;
+        mAms.mActiveUids.put(uid, record);
+        return record;
+    }
+
+    static class CustomThread extends Thread {
+        private static final long WAIT_TIMEOUT_MS = 1000;
+        private static final long WAIT_INTERVAL_MS = 100;
+
+        private final Object mLock;
+        private Runnable mRunnable;
+        boolean mNotified;
+
+        public CustomThread(Object lock) {
+            mLock = lock;
+        }
+
+        public CustomThread(Object lock, Runnable runnable) {
+            super(runnable);
+            mLock = lock;
+            mRunnable = runnable;
+        }
+
+        @Override
+        public void run() {
+            if (mRunnable != null) {
+                mRunnable.run();
+            } else {
+                synchronized (mLock) {
+                    try {
+                        mLock.wait();
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupted();
+                    }
+                }
+            }
+            mNotified = !Thread.interrupted();
+        }
+
+        public void startAndWait(String errMsg) throws Exception {
+            startAndWait(errMsg, false);
+        }
+
+        public void startAndWait(String errMsg, boolean timedWaiting) throws Exception {
+            start();
+            final long endTime = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MS;
+            final Thread.State stateToReach = timedWaiting
+                    ? Thread.State.TIMED_WAITING : Thread.State.WAITING;
+            while (getState() != stateToReach
+                    && SystemClock.elapsedRealtime() < endTime) {
+                Thread.sleep(WAIT_INTERVAL_MS);
+            }
+            if (timedWaiting) {
+                assertTimedWaiting(errMsg);
+            } else {
+                assertWaiting(errMsg);
+            }
+        }
+
+        public void assertWaiting(String errMsg) {
+            assertEquals(errMsg, Thread.State.WAITING, getState());
+        }
+
+        public void assertTimedWaiting(String errMsg) {
+            assertEquals(errMsg, Thread.State.TIMED_WAITING, getState());
+        }
+
+        public void assertTerminated(String errMsg) throws Exception {
+            final long endTime = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MS;
+            while (getState() != Thread.State.TERMINATED
+                    && SystemClock.elapsedRealtime() < endTime) {
+                Thread.sleep(WAIT_INTERVAL_MS);
+            }
+            assertEquals(errMsg, Thread.State.TERMINATED, getState());
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
new file mode 100644
index 0000000..c70d1e1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -0,0 +1,833 @@
+/*
+ * Copyright (C) 2017 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.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.util.DebugUtils.valueToString;
+import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
+import static com.android.server.am.ActivityManagerService.DISPATCH_UIDS_CHANGED_UI_MSG;
+import static com.android.server.am.ActivityManagerService.Injector;
+import static com.android.server.am.ActivityManagerService.NETWORK_STATE_BLOCK;
+import static com.android.server.am.ActivityManagerService.NETWORK_STATE_NO_CHANGE;
+import static com.android.server.am.ActivityManagerService.NETWORK_STATE_UNBLOCK;
+
+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.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.IApplicationThread;
+import android.app.IUidObserver;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.AppOpsService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Test class for {@link ActivityManagerService}.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.server.am.ActivityManagerServiceTest frameworks-services
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksServicesTests
+ * Install: adb install -r \
+ *     ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -e class com.android.server.am.ActivityManagerServiceTest -w \
+ *     com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerServiceTest {
+    private static final String TAG = ActivityManagerServiceTest.class.getSimpleName();
+
+    private static final int TEST_UID = 11111;
+
+    private static final long TEST_PROC_STATE_SEQ1 = 555;
+    private static final long TEST_PROC_STATE_SEQ2 = 556;
+
+    private static final int[] UID_RECORD_CHANGES = {
+        UidRecord.CHANGE_PROCSTATE,
+        UidRecord.CHANGE_GONE,
+        UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE,
+        UidRecord.CHANGE_IDLE,
+        UidRecord.CHANGE_ACTIVE
+    };
+
+    @Mock private Context mContext;
+    @Mock private AppOpsService mAppOpsService;
+    @Mock private PackageManager mPackageManager;
+    @Mock private BatteryStatsImpl mBatteryStatsImpl;
+
+    private TestInjector mInjector;
+    private ActivityManagerService mAms;
+    private HandlerThread mHandlerThread;
+    private TestHandler mHandler;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new TestHandler(mHandlerThread.getLooper());
+        mInjector = new TestInjector();
+        mAms = new ActivityManagerService(mInjector);
+        mAms.mWaitForNetworkTimeoutMs = 2000;
+
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    @After
+    public void tearDown() {
+        mHandlerThread.quit();
+    }
+
+    @MediumTest
+    @Test
+    public void incrementProcStateSeqAndNotifyAppsLocked() throws Exception {
+
+        final UidRecord uidRec = addUidRecord(TEST_UID);
+        addUidRecord(TEST_UID + 1);
+
+        // Uid state is not moving from background to foreground or vice versa.
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_TOP, // prevState
+                PROCESS_STATE_TOP, // curState
+                0, // expectedGlobalCounter
+                0, // exptectedCurProcStateSeq
+                NETWORK_STATE_NO_CHANGE, // expectedBlockState
+                false); // expectNotify
+
+        // Uid state is moving from foreground to background.
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_FOREGROUND_SERVICE, // prevState
+                PROCESS_STATE_SERVICE, // curState
+                1, // expectedGlobalCounter
+                1, // exptectedCurProcStateSeq
+                NETWORK_STATE_UNBLOCK, // expectedBlockState
+                true); // expectNotify
+
+        // Explicitly setting the seq counter for more verification.
+        mAms.mProcStateSeqCounter = 42;
+
+        // Uid state is not moving from background to foreground or vice versa.
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState
+                PROCESS_STATE_IMPORTANT_FOREGROUND, // curState
+                42, // expectedGlobalCounter
+                1, // exptectedCurProcStateSeq
+                NETWORK_STATE_NO_CHANGE, // expectedBlockState
+                false); // expectNotify
+
+        // Uid state is moving from background to foreground.
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_LAST_ACTIVITY, // prevState
+                PROCESS_STATE_TOP, // curState
+                43, // expectedGlobalCounter
+                43, // exptectedCurProcStateSeq
+                NETWORK_STATE_BLOCK, // expectedBlockState
+                false); // expectNotify
+
+        // verify waiting threads are not notified.
+        uidRec.waitingForNetwork = false;
+        // Uid state is moving from foreground to background.
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_FOREGROUND_SERVICE, // prevState
+                PROCESS_STATE_SERVICE, // curState
+                44, // expectedGlobalCounter
+                44, // exptectedCurProcStateSeq
+                NETWORK_STATE_UNBLOCK, // expectedBlockState
+                false); // expectNotify
+
+        // Verify when uid is not restricted, procStateSeq is not incremented.
+        uidRec.waitingForNetwork = true;
+        mInjector.setNetworkRestrictedForUid(false);
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState
+                PROCESS_STATE_TOP, // curState
+                44, // expectedGlobalCounter
+                44, // exptectedCurProcStateSeq
+                -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
+                false); // expectNotify
+
+        // Verify when waitForNetworkTimeout is 0, then procStateSeq is not incremented.
+        mAms.mWaitForNetworkTimeoutMs = 0;
+        mInjector.setNetworkRestrictedForUid(true);
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_TOP, // prevState
+                PROCESS_STATE_IMPORTANT_BACKGROUND, // curState
+                44, // expectedGlobalCounter
+                44, // exptectedCurProcStateSeq
+                -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
+                false); // expectNotify
+
+        // Verify when the uid doesn't have internet permission, then procStateSeq is not
+        // incremented.
+        uidRec.hasInternetPermission = false;
+        mAms.mWaitForNetworkTimeoutMs = 111;
+        mInjector.setNetworkRestrictedForUid(true);
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_CACHED_ACTIVITY, // prevState
+                PROCESS_STATE_FOREGROUND_SERVICE, // curState
+                44, // expectedGlobalCounter
+                44, // exptectedCurProcStateSeq
+                -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
+                false); // expectNotify
+
+        // Verify procStateSeq is not incremented when the uid is not an application, regardless
+        // of the process state.
+        final int notAppUid = 111;
+        final UidRecord uidRec2 = addUidRecord(notAppUid);
+        verifySeqCounterAndInteractions(uidRec2,
+                PROCESS_STATE_CACHED_EMPTY, // prevState
+                PROCESS_STATE_TOP, // curState
+                44, // expectedGlobalCounter
+                0, // exptectedCurProcStateSeq
+                -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
+                false); // expectNotify
+    }
+
+    private UidRecord addUidRecord(int uid) {
+        final UidRecord uidRec = new UidRecord(uid);
+        uidRec.waitingForNetwork = true;
+        uidRec.hasInternetPermission = true;
+        mAms.mActiveUids.put(uid, uidRec);
+
+        final ProcessRecord appRec = new ProcessRecord(null, mBatteryStatsImpl,
+                new ApplicationInfo(), TAG, uid);
+        appRec.thread = Mockito.mock(IApplicationThread.class);
+        mAms.mLruProcesses.add(appRec);
+
+        return uidRec;
+    }
+
+    private void verifySeqCounterAndInteractions(UidRecord uidRec, int prevState, int curState,
+            int expectedGlobalCounter, int expectedCurProcStateSeq, int expectedBlockState,
+            boolean expectNotify) throws Exception {
+        CustomThread thread = new CustomThread(uidRec.networkStateLock);
+        thread.startAndWait("Unexpected state for " + uidRec);
+
+        uidRec.setProcState = prevState;
+        uidRec.curProcState = curState;
+        mAms.incrementProcStateSeqAndNotifyAppsLocked();
+
+        assertEquals(expectedGlobalCounter, mAms.mProcStateSeqCounter);
+        assertEquals(expectedCurProcStateSeq, uidRec.curProcStateSeq);
+
+        for (int i = mAms.mLruProcesses.size() - 1; i >= 0; --i) {
+            final ProcessRecord app = mAms.mLruProcesses.get(i);
+            // AMS should notify apps only for block states other than NETWORK_STATE_NO_CHANGE.
+            if (app.uid == uidRec.uid && expectedBlockState == NETWORK_STATE_BLOCK) {
+                verify(app.thread).setNetworkBlockSeq(uidRec.curProcStateSeq);
+            } else {
+                verifyZeroInteractions(app.thread);
+            }
+            Mockito.reset(app.thread);
+        }
+
+        if (expectNotify) {
+            thread.assertTerminated("Unexpected state for " + uidRec);
+        } else {
+            thread.assertWaiting("Unexpected state for " + uidRec);
+            thread.interrupt();
+        }
+    }
+
+    @Test
+    public void testBlockStateForUid() {
+        final UidRecord uidRec = new UidRecord(TEST_UID);
+        int expectedBlockState;
+
+        final String errorTemplate = "Block state should be %s, prevState: %s, curState: %s";
+        Function<Integer, String> errorMsg = (blockState) -> {
+            return String.format(errorTemplate,
+                    valueToString(ActivityManagerService.class, "NETWORK_STATE_", blockState),
+                    valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.setProcState),
+                    valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.curProcState));
+        };
+
+        // No change in uid state
+        uidRec.setProcState = PROCESS_STATE_RECEIVER;
+        uidRec.curProcState = PROCESS_STATE_RECEIVER;
+        expectedBlockState = NETWORK_STATE_NO_CHANGE;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
+
+        // Foreground to foreground
+        uidRec.setProcState = PROCESS_STATE_FOREGROUND_SERVICE;
+        uidRec.curProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+        expectedBlockState = NETWORK_STATE_NO_CHANGE;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
+
+        // Background to background
+        uidRec.setProcState = PROCESS_STATE_CACHED_ACTIVITY;
+        uidRec.curProcState = PROCESS_STATE_CACHED_EMPTY;
+        expectedBlockState = NETWORK_STATE_NO_CHANGE;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
+
+        // Background to background
+        uidRec.setProcState = PROCESS_STATE_NONEXISTENT;
+        uidRec.curProcState = PROCESS_STATE_CACHED_ACTIVITY;
+        expectedBlockState = NETWORK_STATE_NO_CHANGE;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
+
+        // Background to foreground
+        uidRec.setProcState = PROCESS_STATE_SERVICE;
+        uidRec.curProcState = PROCESS_STATE_FOREGROUND_SERVICE;
+        expectedBlockState = NETWORK_STATE_BLOCK;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
+
+        // Foreground to background
+        uidRec.setProcState = PROCESS_STATE_TOP;
+        uidRec.curProcState = PROCESS_STATE_LAST_ACTIVITY;
+        expectedBlockState = NETWORK_STATE_UNBLOCK;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
+    }
+
+    /**
+     * This test verifies that process state changes are dispatched to observers based on the
+     * changes they wanted to listen (this is specified when registering the observer).
+     */
+    @Test
+    public void testDispatchUids_dispatchNeededChanges() throws RemoteException {
+        when(mAppOpsService.noteOperation(AppOpsManager.OP_GET_USAGE_STATS, Process.myUid(), null))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+
+        final int[] changesToObserve = {
+            ActivityManager.UID_OBSERVER_PROCSTATE,
+            ActivityManager.UID_OBSERVER_GONE,
+            ActivityManager.UID_OBSERVER_IDLE,
+            ActivityManager.UID_OBSERVER_ACTIVE,
+            ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
+                    | ActivityManager.UID_OBSERVER_ACTIVE | ActivityManager.UID_OBSERVER_IDLE
+        };
+        final IUidObserver[] observers = new IUidObserver.Stub[changesToObserve.length];
+        for (int i = 0; i < observers.length; ++i) {
+            observers[i] = Mockito.mock(IUidObserver.Stub.class);
+            when(observers[i].asBinder()).thenReturn((IBinder) observers[i]);
+            mAms.registerUidObserver(observers[i], changesToObserve[i] /* which */,
+                    ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */);
+
+            // When we invoke AMS.registerUidObserver, there are some interactions with observers[i]
+            // mock in RemoteCallbackList class. We don't want to test those interactions and
+            // at the same time, we don't want those to interfere with verifyNoMoreInteractions.
+            // So, resetting the mock here.
+            Mockito.reset(observers[i]);
+        }
+
+        // Add pending uid records each corresponding to a different change type UidRecord.CHANGE_*
+        final int[] changesForPendingUidRecords = UID_RECORD_CHANGES;
+
+        final int[] procStatesForPendingUidRecords = {
+            ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+            ActivityManager.PROCESS_STATE_NONEXISTENT,
+            ActivityManager.PROCESS_STATE_CACHED_EMPTY,
+            ActivityManager.PROCESS_STATE_CACHED_ACTIVITY,
+            ActivityManager.PROCESS_STATE_TOP
+        };
+        final Map<Integer, UidRecord.ChangeItem> changeItems = new HashMap<>();
+        for (int i = 0; i < changesForPendingUidRecords.length; ++i) {
+            final UidRecord.ChangeItem pendingChange = new UidRecord.ChangeItem();
+            pendingChange.change = changesForPendingUidRecords[i];
+            pendingChange.uid = i;
+            pendingChange.processState = procStatesForPendingUidRecords[i];
+            pendingChange.procStateSeq = i;
+            changeItems.put(changesForPendingUidRecords[i], pendingChange);
+            mAms.mPendingUidChanges.add(pendingChange);
+        }
+
+        mAms.dispatchUidsChanged();
+        // Verify the required changes have been dispatched to observers.
+        for (int i = 0; i < observers.length; ++i) {
+            final int changeToObserve = changesToObserve[i];
+            final IUidObserver observerToTest = observers[i];
+            if ((changeToObserve & ActivityManager.UID_OBSERVER_IDLE) != 0) {
+                // Observer listens to uid idle changes, so change items corresponding to
+                // UidRecord.CHANGE_IDLE or UidRecord.CHANGE_IDLE_GONE needs to be
+                // delivered to this observer.
+                final int[] changesToVerify = {
+                    UidRecord.CHANGE_IDLE,
+                    UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE
+                };
+                verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems,
+                        (observer, changeItem) -> {
+                            verify(observer).onUidIdle(changeItem.uid, changeItem.ephemeral);
+                        });
+            }
+            if ((changeToObserve & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
+                // Observer listens to uid active changes, so change items corresponding to
+                // UidRecord.CHANGE_ACTIVE needs to be delivered to this observer.
+                final int[] changesToVerify = { UidRecord.CHANGE_ACTIVE };
+                verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems,
+                        (observer, changeItem) -> {
+                            verify(observer).onUidActive(changeItem.uid);
+                        });
+            }
+            if ((changeToObserve & ActivityManager.UID_OBSERVER_GONE) != 0) {
+                // Observer listens to uid gone changes, so change items corresponding to
+                // UidRecord.CHANGE_GONE or UidRecord.CHANGE_IDLE_GONE needs to be
+                // delivered to this observer.
+                final int[] changesToVerify = {
+                        UidRecord.CHANGE_GONE,
+                        UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE
+                };
+                verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems,
+                        (observer, changeItem) -> {
+                            verify(observer).onUidGone(changeItem.uid, changeItem.ephemeral);
+                        });
+            }
+            if ((changeToObserve & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
+                // Observer listens to uid procState changes, so change items corresponding to
+                // UidRecord.CHANGE_PROCSTATE or UidRecord.CHANGE_IDLE or UidRecord.CHANGE_ACTIVE
+                // needs to be delivered to this observer.
+                final int[] changesToVerify = {
+                        UidRecord.CHANGE_PROCSTATE,
+                        UidRecord.CHANGE_ACTIVE,
+                        UidRecord.CHANGE_IDLE
+                };
+                verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems,
+                        (observer, changeItem) -> {
+                            verify(observer).onUidStateChanged(changeItem.uid,
+                                    changeItem.processState, changeItem.procStateSeq);
+                        });
+            }
+            // Verify there are no other callbacks for this observer.
+            verifyNoMoreInteractions(observerToTest);
+        }
+    }
+
+    private interface ObserverChangesVerifier {
+        void verify(IUidObserver observer, UidRecord.ChangeItem changeItem) throws RemoteException;
+    }
+
+    private void verifyObserverReceivedChanges(IUidObserver observer, int[] changesToVerify,
+            Map<Integer, UidRecord.ChangeItem> changeItems, ObserverChangesVerifier verifier)
+            throws RemoteException {
+        for (int change : changesToVerify) {
+            final UidRecord.ChangeItem changeItem = changeItems.get(change);
+            verifier.verify(observer, changeItem);
+        }
+    }
+
+    /**
+     * This test verifies that process state changes are dispatched to observers only when they
+     * change across the cutpoint (this is specified when registering the observer).
+     */
+    @Test
+    public void testDispatchUidChanges_procStateCutpoint() throws RemoteException {
+        final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
+
+        when(observer.asBinder()).thenReturn((IBinder) observer);
+        mAms.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */,
+                ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, null /* callingPackage */);
+        // When we invoke AMS.registerUidObserver, there are some interactions with observer
+        // mock in RemoteCallbackList class. We don't want to test those interactions and
+        // at the same time, we don't want those to interfere with verifyNoMoreInteractions.
+        // So, resetting the mock here.
+        Mockito.reset(observer);
+
+        final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem();
+        changeItem.uid = TEST_UID;
+        changeItem.change = UidRecord.CHANGE_PROCSTATE;
+        changeItem.processState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+        changeItem.procStateSeq = 111;
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
+        // First process state message is always delivered regardless of whether the process state
+        // change is above or below the cutpoint (PROCESS_STATE_SERVICE).
+        verify(observer).onUidStateChanged(TEST_UID,
+                changeItem.processState, changeItem.procStateSeq);
+        verifyNoMoreInteractions(observer);
+
+        changeItem.processState = ActivityManager.PROCESS_STATE_RECEIVER;
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
+        // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
+        // the current process state change is also below cutpoint, so no callback will be invoked.
+        verifyNoMoreInteractions(observer);
+
+        changeItem.processState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
+        // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
+        // the current process state change is above cutpoint, so callback will be invoked with the
+        // current process state change.
+        verify(observer).onUidStateChanged(TEST_UID,
+                changeItem.processState, changeItem.procStateSeq);
+        verifyNoMoreInteractions(observer);
+
+        changeItem.processState = ActivityManager.PROCESS_STATE_TOP;
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
+        // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
+        // the current process state change is also above cutpoint, so no callback will be invoked.
+        verifyNoMoreInteractions(observer);
+
+        changeItem.processState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
+        // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
+        // the current process state change is below cutpoint, so callback will be invoked with the
+        // current process state change.
+        verify(observer).onUidStateChanged(TEST_UID,
+                changeItem.processState, changeItem.procStateSeq);
+        verifyNoMoreInteractions(observer);
+    }
+
+    /**
+     * This test verifies that {@link ActivityManagerService#mValidateUids} which is a
+     * part of dumpsys is correctly updated.
+     */
+    @Test
+    public void testDispatchUidChanges_validateUidsUpdated() {
+        final int[] changesForPendingItems = UID_RECORD_CHANGES;
+
+        final int[] procStatesForPendingItems = {
+            ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+            ActivityManager.PROCESS_STATE_CACHED_EMPTY,
+            ActivityManager.PROCESS_STATE_CACHED_ACTIVITY,
+            ActivityManager.PROCESS_STATE_SERVICE,
+            ActivityManager.PROCESS_STATE_RECEIVER
+        };
+        final ArrayList<UidRecord.ChangeItem> pendingItemsForUids
+                = new ArrayList<>(changesForPendingItems.length);
+        for (int i = 0; i < changesForPendingItems.length; ++i) {
+            final UidRecord.ChangeItem item = new UidRecord.ChangeItem();
+            item.uid = i;
+            item.change = changesForPendingItems[i];
+            item.processState = procStatesForPendingItems[i];
+            pendingItemsForUids.add(i, item);
+        }
+
+        // Verify that when there no observers listening to uid state changes, then there will
+        // be no changes to validateUids.
+        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.dispatchUidsChanged();
+        assertEquals("No observers registered, so validateUids should be empty",
+                0, mAms.mValidateUids.size());
+
+        final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
+        when(observer.asBinder()).thenReturn((IBinder) observer);
+        mAms.registerUidObserver(observer, 0, 0, null);
+        // Verify that when observers are registered, then validateUids is correctly updated.
+        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.dispatchUidsChanged();
+        for (int i = 0; i < pendingItemsForUids.size(); ++i) {
+            final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
+            final UidRecord validateUidRecord = mAms.mValidateUids.get(item.uid);
+            if ((item.change & UidRecord.CHANGE_GONE) != 0) {
+                assertNull("validateUidRecord should be null since the change is either "
+                        + "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord);
+            } else {
+                assertNotNull("validateUidRecord should not be null since the change is neither "
+                        + "CHANGE_GONE nor CHANGE_GONE_IDLE", validateUidRecord);
+                assertEquals("processState: " + item.processState + " curProcState: "
+                        + validateUidRecord.curProcState + " should have been equal",
+                        item.processState, validateUidRecord.curProcState);
+                assertEquals("processState: " + item.processState + " setProcState: "
+                        + validateUidRecord.curProcState + " should have been equal",
+                        item.processState, validateUidRecord.setProcState);
+                if (item.change == UidRecord.CHANGE_IDLE) {
+                    assertTrue("UidRecord.idle should be updated to true for CHANGE_IDLE",
+                            validateUidRecord.idle);
+                } else if (item.change == UidRecord.CHANGE_ACTIVE) {
+                    assertFalse("UidRecord.idle should be updated to false for CHANGE_ACTIVE",
+                            validateUidRecord.idle);
+                }
+            }
+        }
+
+        // Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it
+        // will be removed from validateUids.
+        assertNotEquals("validateUids should not be empty", 0, mAms.mValidateUids.size());
+        for (int i = 0; i < pendingItemsForUids.size(); ++i) {
+            final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
+            // Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd
+            // distribution for this assignment.
+            item.change = (i % 2) == 0 ? (UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE)
+                    : UidRecord.CHANGE_GONE;
+        }
+        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.dispatchUidsChanged();
+        assertEquals("validateUids should be empty, validateUids: " + mAms.mValidateUids,
+                0, mAms.mValidateUids.size());
+    }
+
+    @Test
+    public void testEnqueueUidChangeLocked_procStateSeqUpdated() {
+        final UidRecord uidRecord = new UidRecord(TEST_UID);
+        uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
+
+        // Verify with no pending changes for TEST_UID.
+        verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ1);
+
+        // Add a pending change for TEST_UID and verify enqueueUidChangeLocked still works as
+        // expected.
+        final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem();
+        uidRecord.pendingChange = changeItem;
+        uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ2;
+        verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ2);
+    }
+
+    @Test
+    public void testEnqueueUidChangeLocked_nullUidRecord() {
+        // Use "null" uidRecord to make sure there is no crash.
+        mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
+    }
+
+    private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) {
+        // Test enqueueUidChangeLocked with every UidRecord.CHANGE_*
+        for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) {
+            final int changeToDispatch = UID_RECORD_CHANGES[i];
+            // Reset lastProcStateSeqDispatchToObservers after every test.
+            uidRecord.lastDispatchedProcStateSeq = 0;
+            mAms.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
+            // Verify there is no effect on curProcStateSeq.
+            assertEquals(curProcstateSeq, uidRecord.curProcStateSeq);
+            if ((changeToDispatch & UidRecord.CHANGE_GONE) != 0) {
+                // Since the change is CHANGE_GONE or CHANGE_GONE_IDLE, verify that
+                // lastProcStateSeqDispatchedToObservers is not updated.
+                assertNotEquals(uidRecord.curProcStateSeq,
+                        uidRecord.lastDispatchedProcStateSeq);
+            } else {
+                // Since the change is neither CHANGE_GONE nor CHANGE_GONE_IDLE, verify that
+                // lastProcStateSeqDispatchedToObservers has been updated to curProcStateSeq.
+                assertEquals(uidRecord.curProcStateSeq,
+                        uidRecord.lastDispatchedProcStateSeq);
+            }
+        }
+    }
+
+    @MediumTest
+    @Test
+    public void testEnqueueUidChangeLocked_dispatchUidsChanged() {
+        final UidRecord uidRecord = new UidRecord(TEST_UID);
+        final int expectedProcState = PROCESS_STATE_SERVICE;
+        uidRecord.setProcState = expectedProcState;
+        uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
+
+        // Test with no pending uid records.
+        for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) {
+            final int changeToDispatch = UID_RECORD_CHANGES[i];
+
+            // Reset the current state
+            mHandler.reset();
+            uidRecord.pendingChange = null;
+            mAms.mPendingUidChanges.clear();
+
+            mAms.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
+
+            // Verify that UidRecord.pendingChange is updated correctly.
+            assertNotNull(uidRecord.pendingChange);
+            assertEquals(TEST_UID, uidRecord.pendingChange.uid);
+            assertEquals(expectedProcState, uidRecord.pendingChange.processState);
+            assertEquals(TEST_PROC_STATE_SEQ1, uidRecord.pendingChange.procStateSeq);
+
+            // Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler.
+            mHandler.waitForMessage(DISPATCH_UIDS_CHANGED_UI_MSG);
+        }
+    }
+
+    @MediumTest
+    @Test
+    public void testWaitForNetworkStateUpdate() throws Exception {
+        // Check there is no crash when there is no UidRecord for myUid
+        mAms.waitForNetworkStateUpdate(TEST_PROC_STATE_SEQ1);
+
+        // Verify there is no waiting when UidRecord.curProcStateSeq is greater than
+        // the procStateSeq in the request to wait.
+        verifyWaitingForNetworkStateUpdate(
+                TEST_PROC_STATE_SEQ1, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
+                TEST_PROC_STATE_SEQ1 - 4, // lastNetworkUpdatedProcStateSeq
+                TEST_PROC_STATE_SEQ1 - 2, // procStateSeqToWait
+                false); // expectWait
+
+        // Verify there is no waiting when the procStateSeq in the request to wait is
+        // not dispatched to NPMS.
+        verifyWaitingForNetworkStateUpdate(
+                TEST_PROC_STATE_SEQ1, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1 - 1, // lastDsipatchedProcStateSeq
+                TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq
+                TEST_PROC_STATE_SEQ1, // procStateSeqToWait
+                false); // expectWait
+
+        // Verify there is not waiting when the procStateSeq in the request already has
+        // an updated network state.
+        verifyWaitingForNetworkStateUpdate(
+                TEST_PROC_STATE_SEQ1, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastNetworkUpdatedProcStateSeq
+                TEST_PROC_STATE_SEQ1, // procStateSeqToWait
+                false); // expectWait
+
+        // Verify waiting for network works
+        verifyWaitingForNetworkStateUpdate(
+                TEST_PROC_STATE_SEQ1, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
+                TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq
+                TEST_PROC_STATE_SEQ1, // procStateSeqToWait
+                true); // expectWait
+    }
+
+    private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
+            long lastDispatchedProcStateSeq, long lastNetworkUpdatedProcStateSeq,
+            final long procStateSeqToWait, boolean expectWait) throws Exception {
+        final UidRecord record = new UidRecord(Process.myUid());
+        record.curProcStateSeq = curProcStateSeq;
+        record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq;
+        record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
+        mAms.mActiveUids.put(Process.myUid(), record);
+
+        CustomThread thread = new CustomThread(record.networkStateLock, new Runnable() {
+            @Override
+            public void run() {
+                mAms.waitForNetworkStateUpdate(procStateSeqToWait);
+            }
+        });
+        final String errMsg = "Unexpected state for " + record;
+        if (expectWait) {
+            thread.startAndWait(errMsg, true);
+            thread.assertTimedWaiting(errMsg);
+            synchronized (record.networkStateLock) {
+                record.networkStateLock.notifyAll();
+            }
+            thread.assertTerminated(errMsg);
+            assertTrue(thread.mNotified);
+            assertFalse(record.waitingForNetwork);
+        } else {
+            thread.start();
+            thread.assertTerminated(errMsg);
+        }
+
+        mAms.mActiveUids.clear();
+    }
+
+    private class TestHandler extends Handler {
+        private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec
+        private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec
+
+        private Set<Integer> mMsgsHandled = new HashSet<>();
+
+        TestHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            mMsgsHandled.add(msg.what);
+        }
+
+        public void waitForMessage(int msg) {
+            final long endTime = System.currentTimeMillis() + WAIT_FOR_MSG_TIMEOUT_MS;
+            while (!mMsgsHandled.contains(msg) && System.currentTimeMillis() < endTime) {
+                SystemClock.sleep(WAIT_FOR_MSG_INTERVAL_MS);
+            }
+            if (!mMsgsHandled.contains(msg)) {
+                fail("Timed out waiting for the message to be handled, msg: " + msg);
+            }
+        }
+
+        public void reset() {
+            mMsgsHandled.clear();
+        }
+    }
+
+    private class TestInjector extends Injector {
+        private boolean mRestricted = true;
+
+        @Override
+        public Context getContext() {
+            return mContext;
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File file, Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandler;
+        }
+
+        @Override
+        public boolean isNetworkRestrictedForUid(int uid) {
+            return mRestricted;
+        }
+
+        public void setNetworkRestrictedForUid(boolean restricted) {
+            mRestricted = restricted;
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
new file mode 100644
index 0000000..ba25b16
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 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 android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.RemoteException;
+import android.test.AndroidTestCase;
+
+import java.util.List;
+
+public class ActivityManagerTest extends AndroidTestCase {
+
+    IActivityManager service;
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        service = ActivityManager.getService();
+    }
+
+    public void testTaskIdsForRunningUsers() throws RemoteException {
+        for(int userId : service.getRunningUserIds()) {
+            testTaskIdsForUser(userId);
+        }
+    }
+
+    private void testTaskIdsForUser(int userId) throws RemoteException {
+        List<ActivityManager.RecentTaskInfo> recentTasks = service.getRecentTasks(
+                100, 0, userId).getList();
+        if(recentTasks != null) {
+            for(ActivityManager.RecentTaskInfo recentTask : recentTasks) {
+                int taskId = recentTask.persistentId;
+                assertEquals("The task id " + taskId + " should not belong to user " + userId,
+                        taskId / UserHandle.PER_USER_RANGE, userId);
+            }
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
new file mode 100644
index 0000000..5ee1c40
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2017 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.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.server.am.ActivityStack.ActivityState.DESTROYED;
+import static com.android.server.am.ActivityStack.ActivityState.DESTROYING;
+import static com.android.server.am.ActivityStack.ActivityState.FINISHING;
+import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING;
+import static com.android.server.am.ActivityStack.ActivityState.PAUSED;
+import static com.android.server.am.ActivityStack.ActivityState.PAUSING;
+import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
+import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
+
+import static junit.framework.TestCase.assertNotNull;
+
+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.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityOptions;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.PauseActivityItem;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.MutableBoolean;
+
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+
+/**
+ * Tests for the {@link ActivityRecord} class.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:com.android.server.am.ActivityRecordTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ActivityRecordTests extends ActivityTestsBase {
+    private ActivityManagerService mService;
+    private TestActivityStack mStack;
+    private TaskRecord mTask;
+    private ActivityRecord mActivity;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mService = createActivityManagerService();
+        mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+        mActivity = new ActivityBuilder(mService).setTask(mTask).build();
+    }
+
+    @Test
+    public void testStackCleanupOnClearingTask() throws Exception {
+        mActivity.setTask(null);
+        assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1);
+    }
+
+    @Test
+    public void testStackCleanupOnActivityRemoval() throws Exception {
+        mTask.removeActivity(mActivity);
+        assertEquals(mStack.onActivityRemovedFromStackInvocationCount(),  1);
+    }
+
+    @Test
+    public void testStackCleanupOnTaskRemoval() throws Exception {
+        mStack.removeTask(mTask, null /*reason*/, REMOVE_TASK_MODE_MOVING);
+        // Stack should be gone on task removal.
+        assertNull(mService.mStackSupervisor.getStack(mStack.mStackId));
+    }
+
+    @Test
+    public void testNoCleanupMovingActivityInSameStack() throws Exception {
+        final TaskRecord newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack)
+                .build();
+        mActivity.reparent(newTask, 0, null /*reason*/);
+        assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0);
+    }
+
+    @Test
+    public void testPausingWhenVisibleFromStopped() throws Exception {
+        final MutableBoolean pauseFound = new MutableBoolean(false);
+        doAnswer((InvocationOnMock invocationOnMock) -> {
+            final ClientTransaction transaction = invocationOnMock.getArgument(0);
+            if (transaction.getLifecycleStateRequest() instanceof PauseActivityItem) {
+                pauseFound.value = true;
+            }
+            return null;
+        }).when(mActivity.app.thread).scheduleTransaction(any());
+
+        mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
+
+        // The activity is in the focused stack so it should not move to paused.
+        mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+        assertTrue(mActivity.isState(STOPPED));
+        assertFalse(pauseFound.value);
+
+        // Clear focused stack
+        mActivity.mStackSupervisor.mFocusedStack = null;
+
+        // In the unfocused stack, the activity should move to paused.
+        mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+        assertTrue(mActivity.isState(PAUSING));
+        assertTrue(pauseFound.value);
+
+        // Make sure that the state does not change for current non-stopping states.
+        mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
+
+        mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+
+        assertTrue(mActivity.isState(INITIALIZING));
+
+        // Make sure the state does not change if we are not the current top activity.
+        mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind");
+
+        // Make sure that the state does not change when we have an activity becoming translucent
+        final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        mStack.mTranslucentActivityWaiting = topActivity;
+        mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+
+        assertTrue(mActivity.isState(STOPPED));
+    }
+
+    @Test
+    public void testPositionLimitedAspectRatioNavBarBottom() throws Exception {
+        verifyPositionWithLimitedAspectRatio(NAV_BAR_BOTTOM, new Rect(0, 0, 1000, 2000), 1.5f,
+                new Rect(0, 0, 1000, 1500));
+    }
+
+    @Test
+    public void testPositionLimitedAspectRatioNavBarLeft() throws Exception {
+        verifyPositionWithLimitedAspectRatio(NAV_BAR_LEFT, new Rect(0, 0, 2000, 1000), 1.5f,
+                new Rect(500, 0, 2000, 1000));
+    }
+
+    @Test
+    public void testPositionLimitedAspectRatioNavBarRight() throws Exception {
+        verifyPositionWithLimitedAspectRatio(NAV_BAR_RIGHT, new Rect(0, 0, 2000, 1000), 1.5f,
+                new Rect(0, 0, 1500, 1000));
+    }
+
+    private void verifyPositionWithLimitedAspectRatio(int navBarPosition, Rect taskBounds,
+            float aspectRatio, Rect expectedActivityBounds) {
+        // Verify with nav bar on the right.
+        when(mService.mWindowManager.getNavBarPosition()).thenReturn(navBarPosition);
+        mTask.getConfiguration().windowConfiguration.setAppBounds(taskBounds);
+        mActivity.info.maxAspectRatio = aspectRatio;
+        mActivity.ensureActivityConfiguration(
+                0 /* globalChanges */, false /* preserveWindow */);
+        assertEquals(expectedActivityBounds, mActivity.getBounds());
+    }
+
+    @Test
+    public void testCanBeLaunchedOnDisplay() throws Exception {
+        testSupportsLaunchingResizeable(false /*taskPresent*/, true /*taskResizeable*/,
+                true /*activityResizeable*/, true /*expected*/);
+
+        testSupportsLaunchingResizeable(false /*taskPresent*/, true /*taskResizeable*/,
+                false /*activityResizeable*/, false /*expected*/);
+
+        testSupportsLaunchingResizeable(true /*taskPresent*/, false /*taskResizeable*/,
+                true /*activityResizeable*/, false /*expected*/);
+
+        testSupportsLaunchingResizeable(true /*taskPresent*/, true /*taskResizeable*/,
+                false /*activityResizeable*/, true /*expected*/);
+    }
+
+    @Test
+    public void testsApplyOptionsLocked() {
+        ActivityOptions activityOptions = ActivityOptions.makeBasic();
+
+        // Set and apply options for ActivityRecord. Pending options should be cleared
+        mActivity.updateOptionsLocked(activityOptions);
+        mActivity.applyOptionsLocked();
+        assertNull(mActivity.pendingOptions);
+
+        // Set options for two ActivityRecords in same Task. Apply one ActivityRecord options.
+        // Pending options should be cleared for both ActivityRecords
+        ActivityRecord activity2 = new ActivityBuilder(mService).setTask(mTask).build();
+        activity2.updateOptionsLocked(activityOptions);
+        mActivity.updateOptionsLocked(activityOptions);
+        mActivity.applyOptionsLocked();
+        assertNull(mActivity.pendingOptions);
+        assertNull(activity2.pendingOptions);
+
+        // Set options for two ActivityRecords in separate Tasks. Apply one ActivityRecord options.
+        // Pending options should be cleared for only ActivityRecord that was applied
+        TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+        activity2 = new ActivityBuilder(mService).setTask(task2).build();
+        activity2.updateOptionsLocked(activityOptions);
+        mActivity.updateOptionsLocked(activityOptions);
+        mActivity.applyOptionsLocked();
+        assertNull(mActivity.pendingOptions);
+        assertNotNull(activity2.pendingOptions);
+    }
+
+    private void testSupportsLaunchingResizeable(boolean taskPresent, boolean taskResizeable,
+            boolean activityResizeable, boolean expected) {
+        mService.mSupportsMultiWindow = true;
+
+        final TaskRecord task = taskPresent
+                ? new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build() : null;
+
+        if (task != null) {
+            task.setResizeMode(taskResizeable ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE);
+        }
+
+        final ActivityRecord record = new ActivityBuilder(mService).setTask(task).build();
+        record.info.resizeMode = activityResizeable
+                ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE;
+
+        record.canBeLaunchedOnDisplay(DEFAULT_DISPLAY);
+
+
+        verify(mService.mStackSupervisor, times(1)).canPlaceEntityOnDisplay(anyInt(), eq(expected),
+                anyInt(), anyInt(), eq(record.info));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
new file mode 100644
index 0000000..1ce41a6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2017 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.ActivityManager.START_DELIVERED_TO_TOP;
+import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
+import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+
+import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
+import static com.android.server.am.ActivityStackSupervisor
+        .MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE;
+
+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.ArgumentMatchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityOptions;
+import android.app.WaitResult;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseIntArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+
+import java.util.ArrayList;
+
+/**
+ * Tests for the {@link ActivityStackSupervisor} class.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:com.android.server.am.ActivityStackSupervisorTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ActivityStackSupervisorTests extends ActivityTestsBase {
+    private ActivityManagerService mService;
+    private ActivityStackSupervisor mSupervisor;
+    private ActivityStack mFullscreenStack;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mService = createActivityManagerService();
+        mSupervisor = mService.mStackSupervisor;
+        mFullscreenStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+    }
+
+    /**
+     * This test ensures that we do not try to restore a task based off an invalid task id. The
+     * stack supervisor is a test version so there will be no tasks present. We should expect
+     * {@code null} to be returned in this case.
+     */
+    @Test
+    public void testRestoringInvalidTask() throws Exception {
+        TaskRecord task = mSupervisor.anyTaskForIdLocked(0 /*taskId*/,
+                MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */);
+        assertNull(task);
+    }
+
+    /**
+     * This test ensures that an existing task in the pinned stack is moved to the fullscreen
+     * activity stack when a new task is added.
+     */
+    @Test
+    public void testReplacingTaskInPinnedStack() throws Exception {
+        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(mFullscreenStack).build();
+        final TaskRecord firstTask = firstActivity.getTask();
+
+        final ActivityRecord secondActivity = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(mFullscreenStack).build();
+        final TaskRecord secondTask = secondActivity.getTask();
+
+        mSupervisor.setFocusStackUnchecked("testReplacingTaskInPinnedStack", mFullscreenStack);
+
+        // Ensure full screen stack has both tasks.
+        ensureStackPlacement(mFullscreenStack, firstTask, secondTask);
+
+        // Move first activity to pinned stack.
+        final Rect sourceBounds = new Rect();
+        mSupervisor.moveActivityToPinnedStackLocked(firstActivity, sourceBounds,
+                0f /*aspectRatio*/, "initialMove");
+
+        final ActivityDisplay display = mFullscreenStack.getDisplay();
+        ActivityStack pinnedStack = display.getPinnedStack();
+        // Ensure a task has moved over.
+        ensureStackPlacement(pinnedStack, firstTask);
+        ensureStackPlacement(mFullscreenStack, secondTask);
+
+        // Move second activity to pinned stack.
+        mSupervisor.moveActivityToPinnedStackLocked(secondActivity, sourceBounds,
+                0f /*aspectRatio*/, "secondMove");
+
+        // Need to get stacks again as a new instance might have been created.
+        pinnedStack = display.getPinnedStack();
+        mFullscreenStack = display.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        // Ensure stacks have swapped tasks.
+        ensureStackPlacement(pinnedStack, secondTask);
+        ensureStackPlacement(mFullscreenStack, firstTask);
+    }
+
+    private static void ensureStackPlacement(ActivityStack stack, TaskRecord... tasks) {
+        final ArrayList<TaskRecord> stackTasks = stack.getAllTasks();
+        assertEquals(stackTasks.size(), tasks != null ? tasks.length : 0);
+
+        if (tasks == null) {
+            return;
+        }
+
+        for (TaskRecord task : tasks) {
+            assertTrue(stackTasks.contains(task));
+        }
+    }
+
+    /**
+     * Ensures that an activity is removed from the stopping activities list once it is resumed.
+     */
+    @Test
+    public void testStoppingActivityRemovedWhenResumed() throws Exception {
+        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(mFullscreenStack).build();
+        mSupervisor.mStoppingActivities.add(firstActivity);
+
+        firstActivity.completeResumeLocked();
+
+        assertFalse(mSupervisor.mStoppingActivities.contains(firstActivity));
+    }
+
+    /**
+     * Ensures that waiting results are notified of launches.
+     */
+    @Test
+    public void testReportWaitingActivityLaunchedIfNeeded() throws Exception {
+        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(mFullscreenStack).build();
+
+        // #notifyAll will be called on the ActivityManagerService. we must hold the object lock
+        // when this happens.
+        synchronized (mSupervisor.mService) {
+            final WaitResult taskToFrontWait = new WaitResult();
+            mSupervisor.mWaitingActivityLaunched.add(taskToFrontWait);
+            mSupervisor.reportWaitingActivityLaunchedIfNeeded(firstActivity, START_TASK_TO_FRONT);
+
+            assertTrue(mSupervisor.mWaitingActivityLaunched.isEmpty());
+            assertEquals(taskToFrontWait.result, START_TASK_TO_FRONT);
+            assertEquals(taskToFrontWait.who, null);
+
+            final WaitResult deliverToTopWait = new WaitResult();
+            mSupervisor.mWaitingActivityLaunched.add(deliverToTopWait);
+            mSupervisor.reportWaitingActivityLaunchedIfNeeded(firstActivity,
+                    START_DELIVERED_TO_TOP);
+
+            assertTrue(mSupervisor.mWaitingActivityLaunched.isEmpty());
+            assertEquals(deliverToTopWait.result, START_DELIVERED_TO_TOP);
+            assertEquals(deliverToTopWait.who, firstActivity.realActivity);
+        }
+    }
+
+    @Test
+    public void testApplySleepTokensLocked() throws Exception {
+        final ActivityDisplay display = mSupervisor.getDefaultDisplay();
+        final KeyguardController keyguard = mSupervisor.getKeyguardController();
+        final ActivityStack stack = mock(ActivityStack.class);
+        display.addChild(stack, 0 /* position */);
+
+        // Make sure we wake and resume in the case the display is turning on and the keyguard is
+        // not showing.
+        verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/,
+                false /* displayShouldSleep */, true /* isFocusedStack */,
+                false /* keyguardShowing */, true /* expectWakeFromSleep */,
+                true /* expectResumeTopActivity */);
+
+        // Make sure we wake and don't resume when the display is turning on and the keyguard is
+        // showing.
+        verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/,
+                false /* displayShouldSleep */, true /* isFocusedStack */,
+                true /* keyguardShowing */, true /* expectWakeFromSleep */,
+                false /* expectResumeTopActivity */);
+
+        // Make sure we wake and don't resume when the display is turning on and the keyguard is
+        // not showing as unfocused.
+        verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/,
+                false /* displayShouldSleep */, false /* isFocusedStack */,
+                false /* keyguardShowing */, true /* expectWakeFromSleep */,
+                false /* expectResumeTopActivity */);
+
+        // Should not do anything if the display state hasn't changed.
+        verifySleepTokenBehavior(display, keyguard, stack, false /*displaySleeping*/,
+                false /* displayShouldSleep */, true /* isFocusedStack */,
+                false /* keyguardShowing */, false /* expectWakeFromSleep */,
+                false /* expectResumeTopActivity */);
+    }
+
+    private void verifySleepTokenBehavior(ActivityDisplay display, KeyguardController keyguard,
+            ActivityStack stack, boolean displaySleeping, boolean displayShouldSleep,
+            boolean isFocusedStack, boolean keyguardShowing, boolean expectWakeFromSleep,
+            boolean expectResumeTopActivity) {
+        reset(stack);
+
+        doReturn(displayShouldSleep).when(display).shouldSleep();
+        doReturn(displaySleeping).when(display).isSleeping();
+        doReturn(keyguardShowing).when(keyguard).isKeyguardOrAodShowing(anyInt());
+
+        mSupervisor.mFocusedStack = isFocusedStack ? stack : null;
+        mSupervisor.applySleepTokensLocked(true);
+        verify(stack, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked();
+        verify(stack, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked(
+                null /* target */, null /* targetOptions */);
+    }
+
+    @Test
+    public void testTopRunningActivityLockedWithNonExistentDisplay() throws Exception {
+        // Create display that ActivityManagerService does not know about
+        final int unknownDisplayId = 100;
+
+        doAnswer((InvocationOnMock invocationOnMock) -> {
+            final SparseIntArray displayIds = invocationOnMock.<SparseIntArray>getArgument(0);
+            displayIds.put(0, unknownDisplayId);
+            return null;
+        }).when(mSupervisor.mWindowManager).getDisplaysInFocusOrder(any());
+
+        mSupervisor.mFocusedStack = mock(ActivityStack.class);
+
+        // Supervisor should skip over the non-existent display.
+        assertEquals(null, mSupervisor.topRunningActivityLocked());
+    }
+
+    /**
+     * Verifies that removal of activity with task and stack is done correctly.
+     */
+    @Test
+    public void testRemovingStackOnAppCrash() throws Exception {
+        final ActivityDisplay defaultDisplay = mService.mStackSupervisor.getDefaultDisplay();
+        final int originalStackCount = defaultDisplay.getChildCount();
+        final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(stack).build();
+
+        assertEquals(originalStackCount + 1, defaultDisplay.getChildCount());
+
+        // Let's pretend that the app has crashed.
+        firstActivity.app.thread = null;
+        mService.mStackSupervisor.finishTopCrashedActivitiesLocked(firstActivity.app, "test");
+
+        // Verify that the stack was removed.
+        assertEquals(originalStackCount, defaultDisplay.getChildCount());
+    }
+
+    @Test
+    public void testFocusability() throws Exception {
+        final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(stack).build();
+
+        // Under split screen primary we should be focusable when not minimized
+        mService.mStackSupervisor.setDockedStackMinimized(false);
+        assertTrue(stack.isFocusable());
+        assertTrue(activity.isFocusable());
+
+        // Under split screen primary we should not be focusable when minimized
+        mService.mStackSupervisor.setDockedStackMinimized(true);
+        assertFalse(stack.isFocusable());
+        assertFalse(activity.isFocusable());
+
+        final ActivityStack pinnedStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final ActivityRecord pinnedActivity = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(pinnedStack).build();
+
+        // We should not be focusable when in pinned mode
+        assertFalse(pinnedStack.isFocusable());
+        assertFalse(pinnedActivity.isFocusable());
+
+        // Add flag forcing focusability.
+        pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE;
+
+        // We should not be focusable when in pinned mode
+        assertTrue(pinnedStack.isFocusable());
+        assertTrue(pinnedActivity.isFocusable());
+
+        // Without the overridding activity, stack should not be focusable.
+        pinnedStack.removeTask(pinnedActivity.getTask(), "testFocusability",
+                REMOVE_TASK_MODE_DESTROYING);
+        assertFalse(pinnedStack.isFocusable());
+    }
+
+    /**
+     * Verifies the correct activity is returned when querying the top running activity.
+     */
+    @Test
+    public void testTopRunningActivity() throws Exception {
+        // Create stack to hold focus
+        final ActivityStack emptyStack = mService.mStackSupervisor.getDefaultDisplay()
+                .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        final KeyguardController keyguard = mSupervisor.getKeyguardController();
+        final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(stack).build();
+
+        mSupervisor.mFocusedStack = emptyStack;
+
+        doAnswer((InvocationOnMock invocationOnMock) -> {
+            final SparseIntArray displayIds = invocationOnMock.<SparseIntArray>getArgument(0);
+            displayIds.put(0, mSupervisor.getDefaultDisplay().mDisplayId);
+            return null;
+        }).when(mSupervisor.mWindowManager).getDisplaysInFocusOrder(any());
+
+        // Make sure the top running activity is not affected when keyguard is not locked
+        assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked());
+        assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked(
+                true /* considerKeyguardState */));
+
+        // Check to make sure activity not reported when it cannot show on lock and lock is on.
+        doReturn(true).when(keyguard).isKeyguardLocked();
+        assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked());
+        assertEquals(null, mService.mStackSupervisor.topRunningActivityLocked(
+                true /* considerKeyguardState */));
+
+        // Change focus to stack with activity.
+        mSupervisor.mFocusedStack = stack;
+        assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked());
+        assertEquals(null, mService.mStackSupervisor.topRunningActivityLocked(
+                true /* considerKeyguardState */));
+
+        // Add activity that should be shown on the keyguard.
+        final ActivityRecord showWhenLockedActivity = new ActivityBuilder(mService)
+                .setCreateTask(true)
+                .setStack(stack)
+                .setActivityFlags(FLAG_SHOW_WHEN_LOCKED)
+                .build();
+
+        // Ensure the show when locked activity is returned.
+        assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked());
+        assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked(
+                true /* considerKeyguardState */));
+
+        // Change focus back to empty stack
+        mSupervisor.mFocusedStack = emptyStack;
+        // Ensure the show when locked activity is returned when not the focused stack
+        assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked());
+        assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked(
+                true /* considerKeyguardState */));
+    }
+
+    /**
+     * Verify that split-screen primary stack will be chosen if activity is launched that targets
+     * split-screen secondary, but a matching existing instance is found on top of split-screen
+     * primary stack.
+     */
+    @Test
+    public void testSplitScreenPrimaryChosenWhenTopActivityLaunchedToSecondary() throws Exception {
+        // Create primary split-screen stack with a task and an activity.
+        final ActivityStack primaryStack = mService.mStackSupervisor.getDefaultDisplay()
+                .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
+                        true /* onTop */);
+        final TaskRecord task = new TaskBuilder(mSupervisor).setStack(primaryStack).build();
+        final ActivityRecord r = new ActivityBuilder(mService).setTask(task).build();
+
+        // Find a launch stack for the top activity in split-screen primary, while requesting
+        // split-screen secondary.
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+        final ActivityStack result = mSupervisor.getLaunchStack(r, options, task, true /* onTop */);
+
+        // Assert that the primary stack is returned.
+        assertEquals(primaryStack, result);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
new file mode 100644
index 0000000..01425ed
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2017 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_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+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.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+
+import static com.android.server.am.ActivityStack.ActivityState.PAUSING;
+import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.pm.ActivityInfo;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for the {@link ActivityStack} class.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:com.android.server.am.ActivityStackTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ActivityStackTests extends ActivityTestsBase {
+    private ActivityManagerService mService;
+    private ActivityStackSupervisor mSupervisor;
+    private ActivityDisplay mDefaultDisplay;
+    private ActivityStack mStack;
+    private TaskRecord mTask;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mService = createActivityManagerService();
+        mSupervisor = mService.mStackSupervisor;
+        mDefaultDisplay = mService.mStackSupervisor.getDefaultDisplay();
+        mStack = mDefaultDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        mTask = new TaskBuilder(mSupervisor).setStack(mStack).build();
+    }
+
+    @Test
+    public void testEmptyTaskCleanupOnRemove() throws Exception {
+        assertNotNull(mTask.getWindowContainerController());
+        mStack.removeTask(mTask, "testEmptyTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING);
+        assertNull(mTask.getWindowContainerController());
+    }
+
+    @Test
+    public void testOccupiedTaskCleanupOnRemove() throws Exception {
+        final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+        assertNotNull(mTask.getWindowContainerController());
+        mStack.removeTask(mTask, "testOccupiedTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING);
+        assertNotNull(mTask.getWindowContainerController());
+    }
+
+    @Test
+    public void testResumedActivity() throws Exception {
+        final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+        assertEquals(mStack.getResumedActivity(), null);
+        r.setState(RESUMED, "testResumedActivity");
+        assertEquals(mStack.getResumedActivity(), r);
+        r.setState(PAUSING, "testResumedActivity");
+        assertEquals(mStack.getResumedActivity(), null);
+    }
+
+    @Test
+    public void testResumedActivityFromTaskReparenting() {
+        final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+        // Ensure moving task between two stacks updates resumed activity
+        r.setState(RESUMED, "testResumedActivityFromTaskReparenting");
+        assertEquals(mStack.getResumedActivity(), r);
+
+        final ActivityStack destStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        mTask.reparent(destStack, true /* toTop */, TaskRecord.REPARENT_KEEP_STACK_AT_FRONT,
+                false /* animate */, true /* deferResume*/,
+                "testResumedActivityFromTaskReparenting");
+
+        assertEquals(mStack.getResumedActivity(), null);
+        assertEquals(destStack.getResumedActivity(), r);
+    }
+
+    @Test
+    public void testResumedActivityFromActivityReparenting() {
+        final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+        // Ensure moving task between two stacks updates resumed activity
+        r.setState(RESUMED, "testResumedActivityFromActivityReparenting");
+        assertEquals(mStack.getResumedActivity(), r);
+
+        final ActivityStack destStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final TaskRecord destTask = new TaskBuilder(mSupervisor).setStack(destStack).build();
+
+        mTask.removeActivity(r);
+        destTask.addActivityToTop(r);
+
+        assertEquals(mStack.getResumedActivity(), null);
+        assertEquals(destStack.getResumedActivity(), r);
+    }
+
+    @Test
+    public void testPrimarySplitScreenToFullscreenWhenMovedToBack() throws Exception {
+        // Create primary splitscreen stack. This will create secondary stacks and places the
+        // existing fullscreen stack on the bottom.
+        final ActivityStack primarySplitScreen = mDefaultDisplay.createStack(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        // Assert windowing mode.
+        assertEquals(primarySplitScreen.getWindowingMode(), WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Move primary to back.
+        primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack",
+                null /* task */);
+
+        // Assert that stack is at the bottom.
+        assertEquals(mDefaultDisplay.getIndexOf(primarySplitScreen), 0);
+
+        // Ensure no longer in splitscreen.
+        assertEquals(primarySplitScreen.getWindowingMode(), WINDOWING_MODE_FULLSCREEN);
+    }
+
+    @Test
+    public void testStopActivityWhenActivityDestroyed() throws Exception {
+        final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+        r.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
+        mSupervisor.setFocusStackUnchecked("testStopActivityWithDestroy", mStack);
+        mStack.stopActivityLocked(r);
+        // Mostly testing to make sure there is a crash in the call part, so if we get here we are
+        // good-to-go!
+    }
+
+    @Test
+    public void testFindTaskWithOverlay() throws Exception {
+        final ActivityRecord r = new ActivityBuilder(mService)
+                .setCreateTask(true)
+                .setStack(mStack)
+                .setUid(0)
+                .build();
+        final TaskRecord task = r.getTask();
+        // Overlay must be for a different user to prevent recognizing a matching top activity
+        final ActivityRecord taskOverlay = new ActivityBuilder(mService).setTask(task)
+                .setUid(UserHandle.PER_USER_RANGE * 2).build();
+        taskOverlay.mTaskOverlay = true;
+
+        final ActivityStackSupervisor.FindTaskResult result =
+                new ActivityStackSupervisor.FindTaskResult();
+        mStack.findTaskLocked(r, result);
+
+        assertEquals(task.getTopActivity(false /* includeOverlays */), r);
+        assertEquals(task.getTopActivity(true /* includeOverlays */), taskOverlay);
+        assertNotNull(result.r);
+    }
+
+    @Test
+    public void testShouldBeVisible_Fullscreen() throws Exception {
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        assertTrue(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+
+        final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack
+        // should be visible since it is always on-top.
+        fullscreenStack.setIsTranslucent(false);
+        assertFalse(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+        assertTrue(fullscreenStack.shouldBeVisible(null /* starting */));
+
+        // Home stack should be visible behind a translucent fullscreen stack.
+        fullscreenStack.setIsTranslucent(true);
+        assertTrue(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+    }
+
+    @Test
+    public void testShouldBeVisible_SplitScreen() throws Exception {
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        // Home stack should always be fullscreen for this test.
+        homeStack.setSupportsSplitScreen(false);
+        final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        // Home stack shouldn't be visible if both halves of split-screen are opaque.
+        splitScreenPrimary.setIsTranslucent(false);
+        splitScreenSecondary.setIsTranslucent(false);
+        assertFalse(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+
+        // Home stack should be visible if one of the halves of split-screen is translucent.
+        splitScreenPrimary.setIsTranslucent(true);
+        assertTrue(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+
+        final TestActivityStack splitScreenSecondary2 = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        // First split-screen secondary shouldn't be visible behind another opaque split-split
+        // secondary.
+        splitScreenSecondary2.setIsTranslucent(false);
+        assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+        // First split-screen secondary should be visible behind another translucent split-screen
+        // secondary.
+        splitScreenSecondary2.setIsTranslucent(true);
+        assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+        final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
+
+        // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack.
+        assistantStack.setIsTranslucent(false);
+        assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+        assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+        // Split-screen stacks should be visible behind a translucent fullscreen stack.
+        assistantStack.setIsTranslucent(true);
+        assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+        // Assistant stack shouldn't be visible behind translucent split-screen stack
+        assistantStack.setIsTranslucent(false);
+        splitScreenPrimary.setIsTranslucent(true);
+        splitScreenSecondary2.setIsTranslucent(true);
+        splitScreenSecondary2.moveToFront("testShouldBeVisible_SplitScreen");
+        splitScreenPrimary.moveToFront("testShouldBeVisible_SplitScreen");
+        assertFalse(assistantStack.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+    }
+
+    @Test
+    public void testShouldBeVisible_Finishing() throws Exception {
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        final TestActivityStack translucentStack = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        translucentStack.setIsTranslucent(true);
+
+        assertTrue(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+
+        final ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked();
+        topRunningHomeActivity.finishing = true;
+        final ActivityRecord topRunningTranslucentActivity =
+                translucentStack.topRunningActivityLocked();
+        topRunningTranslucentActivity.finishing = true;
+
+        // Home shouldn't be visible since its activity is marked as finishing and it isn't the top
+        // of the stack list.
+        assertFalse(homeStack.shouldBeVisible(null /* starting */));
+        // Home should be visible if we are starting an activity within it.
+        assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */));
+        // The translucent stack should be visible since it is the top of the stack list even though
+        // it has its activity marked as finishing.
+        assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+    }
+
+    @Test
+    public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() {
+        mDefaultDisplay.removeChild(mStack);
+
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        homeStack.setIsTranslucent(false);
+        fullscreenStack.setIsTranslucent(false);
+
+        // Ensure that we don't move the home stack if it is already behind the top fullscreen stack
+        int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
+        assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack);
+        mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack);
+        assertTrue(mDefaultDisplay.getIndexOf(homeStack) == homeStackIndex);
+    }
+
+    @Test
+    public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() {
+        mDefaultDisplay.removeChild(mStack);
+
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        homeStack.setIsTranslucent(false);
+        fullscreenStack.setIsTranslucent(true);
+
+        // Ensure that we don't move the home stack if it is already behind the top fullscreen stack
+        int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
+        assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack);
+        mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack);
+        assertTrue(mDefaultDisplay.getIndexOf(homeStack) == homeStackIndex);
+    }
+
+    @Test
+    public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() {
+        mDefaultDisplay.removeChild(mStack);
+
+        final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+
+        homeStack.setIsTranslucent(false);
+        fullscreenStack.setIsTranslucent(false);
+
+        // Ensure we don't move the home stack if it is already on top
+        int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
+        assertTrue(mDefaultDisplay.getStackAbove(homeStack) == null);
+        mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack);
+        assertTrue(mDefaultDisplay.getIndexOf(homeStack) == homeStackIndex);
+    }
+
+    @Test
+    public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() {
+        mDefaultDisplay.removeChild(mStack);
+
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        final TestActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        homeStack.setIsTranslucent(false);
+        fullscreenStack1.setIsTranslucent(false);
+        fullscreenStack2.setIsTranslucent(false);
+
+        // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the
+        // pinned stack
+        assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack1);
+        mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack);
+        assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack2);
+    }
+
+    @Test
+    public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() {
+        mDefaultDisplay.removeChild(mStack);
+
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+
+        homeStack.setIsTranslucent(false);
+        fullscreenStack1.setIsTranslucent(false);
+        fullscreenStack2.setIsTranslucent(true);
+
+        // Ensure that we move the home stack behind the bottom most non-translucent fullscreen
+        // stack
+        assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack1);
+        mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack);
+        assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack1);
+    }
+
+    @Test
+    public void testMoveHomeStackBehindStack_BehindHomeStack() {
+        mDefaultDisplay.removeChild(mStack);
+
+        final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+
+        homeStack.setIsTranslucent(false);
+        fullscreenStack1.setIsTranslucent(false);
+        fullscreenStack2.setIsTranslucent(false);
+
+        // Ensure we don't move the home stack behind itself
+        int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
+        mDefaultDisplay.moveStackBehindStack(homeStack, homeStack);
+        assertTrue(mDefaultDisplay.getIndexOf(homeStack) == homeStackIndex);
+    }
+
+    @Test
+    public void testMoveHomeStackBehindStack() {
+        mDefaultDisplay.removeChild(mStack);
+
+        final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        final TestActivityStack fullscreenStack3 = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        final TestActivityStack fullscreenStack4 = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+
+        mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack1);
+        assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack1);
+        mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack2);
+        assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack2);
+        mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack4);
+        assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack4);
+        mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack2);
+        assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack2);
+    }
+
+    @Test
+    public void testSplitScreenMoveToFront() throws Exception {
+        final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(
+                mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
+
+        splitScreenPrimary.setIsTranslucent(false);
+        splitScreenSecondary.setIsTranslucent(false);
+        assistantStack.setIsTranslucent(false);
+
+        assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+
+        splitScreenSecondary.moveToFront("testSplitScreenMoveToFront");
+
+        assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertFalse(assistantStack.shouldBeVisible(null /* starting */));
+    }
+
+    private <T extends ActivityStack> T createStackForShouldBeVisibleTest(
+            ActivityDisplay display, int windowingMode, int activityType, boolean onTop) {
+        final T stack = display.createStack(windowingMode, activityType, onTop);
+        final ActivityRecord r = new ActivityBuilder(mService).setUid(0).setStack(stack)
+                .setCreateTask(true).build();
+        return stack;
+    }
+
+    @Test
+    public void testFinishDisabledPackageActivities() throws Exception {
+        final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build();
+
+        // Making the second activity a task overlay without an app means it will be removed from
+        // the task's activities as well once first activity is removed.
+        secondActivity.mTaskOverlay = true;
+        secondActivity.app = null;
+
+        assertEquals(mTask.mActivities.size(), 2);
+
+        mStack.finishDisabledPackageActivitiesLocked(firstActivity.packageName, null,
+                true /* doit */, true /* evenPersistent */, UserHandle.USER_ALL);
+
+        assertTrue(mTask.mActivities.isEmpty());
+        assertTrue(mStack.getAllTasks().isEmpty());
+    }
+
+    @Test
+    public void testHandleAppDied() throws Exception {
+        final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build();
+
+        // Making the first activity a task overlay means it will be removed from the task's
+        // activities as well once second activity is removed as handleAppDied processes the
+        // activity list in reverse.
+        firstActivity.mTaskOverlay = true;
+        firstActivity.app = null;
+
+        // second activity will be immediately removed as it has no state.
+        secondActivity.haveState = false;
+
+        assertEquals(mTask.mActivities.size(), 2);
+
+        mStack.handleAppDiedLocked(secondActivity.app);
+
+        assertTrue(mTask.mActivities.isEmpty());
+        assertTrue(mStack.getAllTasks().isEmpty());
+    }
+
+    @Test
+    public void testShouldSleepActivities() throws Exception {
+        // When focused activity and keyguard is going away, we should not sleep regardless
+        // of the display state
+        verifyShouldSleepActivities(true /* focusedStack */, true /*keyguardGoingAway*/,
+                true /* displaySleeping */, false /* expected*/);
+
+        // When not the focused stack, defer to display sleeping state.
+        verifyShouldSleepActivities(false /* focusedStack */, true /*keyguardGoingAway*/,
+                true /* displaySleeping */, true /* expected*/);
+
+        // If keyguard is going away, defer to the display sleeping state.
+        verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/,
+                true /* displaySleeping */, true /* expected*/);
+        verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/,
+                false /* displaySleeping */, false /* expected*/);
+    }
+
+    @Test
+    public void testStackOrderChangedOnRemoveStack() throws Exception {
+        StackOrderChangedListener listener = new StackOrderChangedListener();
+        mDefaultDisplay.registerStackOrderChangedListener(listener);
+        try {
+            mDefaultDisplay.removeChild(mStack);
+        } finally {
+            mDefaultDisplay.unregisterStackOrderChangedListener(listener);
+        }
+        assertTrue(listener.changed);
+    }
+
+    @Test
+    public void testStackOrderChangedOnAddPositionStack() throws Exception {
+        mDefaultDisplay.removeChild(mStack);
+
+        StackOrderChangedListener listener = new StackOrderChangedListener();
+        mDefaultDisplay.registerStackOrderChangedListener(listener);
+        try {
+            mDefaultDisplay.addChild(mStack, 0);
+        } finally {
+            mDefaultDisplay.unregisterStackOrderChangedListener(listener);
+        }
+        assertTrue(listener.changed);
+    }
+
+    @Test
+    public void testStackOrderChangedOnPositionStack() throws Exception {
+        StackOrderChangedListener listener = new StackOrderChangedListener();
+        try {
+            final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
+                    mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                    true /* onTop */);
+            mDefaultDisplay.registerStackOrderChangedListener(listener);
+            mDefaultDisplay.positionChildAtBottom(fullscreenStack1);
+        } finally {
+            mDefaultDisplay.unregisterStackOrderChangedListener(listener);
+        }
+        assertTrue(listener.changed);
+    }
+
+    private void verifyShouldSleepActivities(boolean focusedStack,
+            boolean keyguardGoingAway, boolean displaySleeping, boolean expected) {
+        mSupervisor.mFocusedStack = focusedStack ? mStack : null;
+
+        final ActivityDisplay display = mock(ActivityDisplay.class);
+        final KeyguardController keyguardController = mSupervisor.getKeyguardController();
+
+        doReturn(display).when(mSupervisor).getActivityDisplay(anyInt());
+        doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway();
+        doReturn(displaySleeping).when(display).isSleeping();
+
+        assertEquals(expected, mStack.shouldSleepActivities());
+    }
+
+    private class StackOrderChangedListener implements ActivityDisplay.OnStackOrderChangedListener {
+        boolean changed = false;
+
+        @Override
+        public void onStackOrderChanged() {
+            changed = true;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java
new file mode 100644
index 0000000..7948e4c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 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 android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch;
+import com.android.server.am.ActivityStarter.Factory;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.times;
+
+import java.util.Random;
+
+/**
+ * Tests for the {@link ActivityStartController} class.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:ActivityStartControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ActivityStartControllerTests extends ActivityTestsBase {
+    private ActivityManagerService mService;
+    private ActivityStartController mController;
+    private Factory mFactory;
+    private ActivityStarter mStarter;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mService = createActivityManagerService();
+        mFactory = mock(Factory.class);
+        mController = new ActivityStartController(mService, mService.mStackSupervisor, mFactory);
+        mStarter = spy(new ActivityStarter(mController, mService, mService.mStackSupervisor,
+                mock(ActivityStartInterceptor.class)));
+        doReturn(mStarter).when(mFactory).obtain();
+    }
+
+    /**
+     * Ensures that pending launches are processed.
+     */
+    @Test
+    public void testPendingActivityLaunches() {
+        final Random random = new Random();
+
+        final ActivityRecord activity = new ActivityBuilder(mService).build();
+        final ActivityRecord source = new ActivityBuilder(mService).build();
+        final int startFlags = random.nextInt();
+        final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final ProcessRecord process= new ProcessRecord(null, null,
+                mService.mContext.getApplicationInfo(), "name", 12345);
+
+        mController.addPendingActivityLaunch(
+                new PendingActivityLaunch(activity, source, startFlags, stack, process));
+        final boolean resume = random.nextBoolean();
+        mController.doPendingActivityLaunches(resume);
+
+        verify(mStarter, times(1)).startResolvedActivity(eq(activity), eq(source), eq(null),
+                eq(null), eq(startFlags), eq(resume), eq(null), eq(null), eq(null));
+    }
+
+
+    /**
+     * Ensures instances are recycled after execution.
+     */
+    @Test
+    public void testRecycling() throws Exception {
+        final Intent intent = new Intent();
+        final ActivityStarter optionStarter = new ActivityStarter(mController, mService,
+                mService.mStackSupervisor, mock(ActivityStartInterceptor.class));
+        optionStarter
+                .setIntent(intent)
+                .setReason("Test")
+                .execute();
+        verify(mFactory, times(1)).recycle(eq(optionStarter));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java
new file mode 100644
index 0000000..b4b34c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2017, 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.content.pm.ApplicationInfo.FLAG_SUSPENDED;
+
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.when;
+
+import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.filters.SmallTest;
+import android.testing.DexmakerShareClassLoaderRule;
+
+import com.android.internal.app.SuspendedAppActivity;
+import com.android.internal.app.UnlaunchableAppActivity;
+import com.android.server.LocalServices;
+import com.android.server.pm.PackageManagerService;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link ActivityStartInterceptorTest}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.am.ActivityStartInterceptorTest
+ */
+@SmallTest
+public class ActivityStartInterceptorTest {
+    private static final int TEST_USER_ID = 1;
+    private static final int TEST_REAL_CALLING_UID = 2;
+    private static final int TEST_REAL_CALLING_PID = 3;
+    private static final String TEST_CALLING_PACKAGE = "com.test.caller";
+    private static final int TEST_START_FLAGS = 4;
+    private static final Intent ADMIN_SUPPORT_INTENT =
+            new Intent("com.test.ADMIN_SUPPORT");
+    private static final Intent CONFIRM_CREDENTIALS_INTENT =
+            new Intent("com.test.CONFIRM_CREDENTIALS");
+    private static final UserInfo PARENT_USER_INFO = new UserInfo(0 /* userId */, "parent",
+            0 /* flags */);
+    private static final String TEST_PACKAGE_NAME = "com.test.package";
+
+    @Rule
+    public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+            new DexmakerShareClassLoaderRule();
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private ActivityManagerService mService;
+    @Mock
+    private ActivityStackSupervisor mSupervisor;
+    @Mock
+    private DevicePolicyManagerInternal mDevicePolicyManager;
+    @Mock
+    private PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private UserController mUserController;
+    @Mock
+    private KeyguardManager mKeyguardManager;
+    @Mock
+    private PackageManagerService mPackageManager;
+
+    private ActivityStartInterceptor mInterceptor;
+    private ActivityInfo mAInfo = new ActivityInfo();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mInterceptor = new ActivityStartInterceptor(mService, mSupervisor, mContext,
+                mUserController);
+        mInterceptor.setStates(TEST_USER_ID, TEST_REAL_CALLING_PID, TEST_REAL_CALLING_UID,
+                TEST_START_FLAGS, TEST_CALLING_PACKAGE);
+
+        // Mock DevicePolicyManagerInternal
+        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+        LocalServices.addService(DevicePolicyManagerInternal.class,
+                mDevicePolicyManager);
+        when(mDevicePolicyManager
+                        .createShowAdminSupportIntent(TEST_USER_ID, true))
+                .thenReturn(ADMIN_SUPPORT_INTENT);
+        when(mService.getPackageManagerInternalLocked()).thenReturn(mPackageManagerInternal);
+
+        // Mock UserManager
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mUserManager.getProfileParent(TEST_USER_ID)).thenReturn(PARENT_USER_INFO);
+
+        // Mock KeyguardManager
+        when(mContext.getSystemService(Context.KEYGUARD_SERVICE)).thenReturn(mKeyguardManager);
+        when(mKeyguardManager.createConfirmDeviceCredentialIntent(
+                nullable(CharSequence.class), nullable(CharSequence.class), eq(TEST_USER_ID))).
+                thenReturn(CONFIRM_CREDENTIALS_INTENT);
+
+        // Mock PackageManager
+        when(mService.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID))
+                .thenReturn(null);
+
+        // Initialise activity info
+        mAInfo.applicationInfo = new ApplicationInfo();
+        mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
+    }
+
+    @Test
+    public void testSuspendedByAdminPackage() {
+        // GIVEN the package we're about to launch is currently suspended
+        mAInfo.applicationInfo.flags = FLAG_SUSPENDED;
+
+        when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID))
+                .thenReturn(PLATFORM_PACKAGE_NAME);
+
+        // THEN calling intercept returns true
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+
+        // THEN the returned intent is the admin support intent
+        assertEquals(ADMIN_SUPPORT_INTENT, mInterceptor.mIntent);
+    }
+
+    @Test
+    public void testSuspendedPackage() {
+        mAInfo.applicationInfo.flags = FLAG_SUSPENDED;
+        final String suspendingPackage = "com.test.suspending.package";
+        final String dialogMessage = "Test Message";
+        when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID))
+                .thenReturn(suspendingPackage);
+        when(mPackageManagerInternal.getSuspendedDialogMessage(TEST_PACKAGE_NAME, TEST_USER_ID))
+                .thenReturn(dialogMessage);
+        // THEN calling intercept returns true
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+
+        // Check intent parameters
+        assertEquals(dialogMessage,
+                mInterceptor.mIntent.getStringExtra(SuspendedAppActivity.EXTRA_DIALOG_MESSAGE));
+        assertEquals(suspendingPackage,
+                mInterceptor.mIntent.getStringExtra(SuspendedAppActivity.EXTRA_SUSPENDING_PACKAGE));
+        assertEquals(TEST_PACKAGE_NAME,
+                mInterceptor.mIntent.getStringExtra(SuspendedAppActivity.EXTRA_SUSPENDED_PACKAGE));
+        assertEquals(TEST_USER_ID, mInterceptor.mIntent.getIntExtra(Intent.EXTRA_USER_ID, -1000));
+    }
+
+    @Test
+    public void testInterceptQuietProfile() {
+        // GIVEN that the user the activity is starting as is currently in quiet mode
+        when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
+
+        // THEN calling intercept returns true
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+
+        // THEN the returned intent is the quiet mode intent
+        assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID)
+                .filterEquals(mInterceptor.mIntent));
+    }
+
+    @Test
+    public void testWorkChallenge() {
+        // GIVEN that the user the activity is starting as is currently locked
+        when(mUserController.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+
+        // THEN calling intercept returns true
+        mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null);
+
+        // THEN the returned intent is the quiet mode intent
+        assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
+    }
+
+    @Test
+    public void testNoInterception() {
+        // GIVEN that none of the interception conditions are met
+
+        // THEN calling intercept returns false
+        assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
new file mode 100644
index 0000000..1520859
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) 2017 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.ActivityManager.START_ABORTED;
+import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
+import static android.app.ActivityManager.START_DELIVERED_TO_TOP;
+import static android.app.ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
+import static android.app.ActivityManager.START_INTENT_NOT_RESOLVED;
+import static android.app.ActivityManager.START_NOT_VOICE_COMPATIBLE;
+import static android.app.ActivityManager.START_PERMISSION_DENIED;
+import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
+import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.ActivityManager.START_SWITCHES_CANCELED;
+import static android.app.ActivityManager.START_TASK_TO_FRONT;
+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.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+
+import android.app.ActivityOptions;
+import android.app.IApplicationThread;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.WindowLayout;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.service.voice.IVoiceInteractionSession;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Gravity;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
+import static com.android.server.am.ActivityManagerService.ANIMATE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.times;
+
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.am.ActivityStarter.Factory;
+import com.android.server.am.LaunchParamsController.LaunchParamsModifier;
+import com.android.server.am.TaskRecord.TaskRecordFactory;
+
+import java.util.ArrayList;
+
+/**
+ * Tests for the {@link ActivityStarter} class.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:ActivityStarterTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ActivityStarterTests extends ActivityTestsBase {
+    private ActivityManagerService mService;
+    private ActivityStarter mStarter;
+    private ActivityStartController mController;
+
+    private static final int PRECONDITION_NO_CALLER_APP = 1;
+    private static final int PRECONDITION_NO_INTENT_COMPONENT = 1 << 1;
+    private static final int PRECONDITION_NO_ACTIVITY_INFO = 1 << 2;
+    private static final int PRECONDITION_SOURCE_PRESENT = 1 << 3;
+    private static final int PRECONDITION_REQUEST_CODE = 1 << 4;
+    private static final int PRECONDITION_SOURCE_VOICE_SESSION = 1 << 5;
+    private static final int PRECONDITION_NO_VOICE_SESSION_SUPPORT = 1 << 6;
+    private static final int PRECONDITION_DIFFERENT_UID = 1 << 7;
+    private static final int PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION = 1 << 8;
+    private static final int PRECONDITION_CANNOT_START_ANY_ACTIVITY = 1 << 9;
+    private static final int PRECONDITION_DISALLOW_APP_SWITCHING = 1 << 10;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mService = createActivityManagerService();
+        mController = mock(ActivityStartController.class);
+        mStarter = new ActivityStarter(mController, mService, mService.mStackSupervisor,
+                mock(ActivityStartInterceptor.class));
+    }
+
+    @Test
+    public void testUpdateLaunchBounds() throws Exception {
+        // When in a non-resizeable stack, the task bounds should be updated.
+        final TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
+                .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */))
+                .build();
+        final Rect bounds = new Rect(10, 10, 100, 100);
+
+        mStarter.updateBounds(task, bounds);
+        assertEquals(task.getOverrideBounds(), bounds);
+        assertEquals(new Rect(), task.getStack().getOverrideBounds());
+
+        // When in a resizeable stack, the stack bounds should be updated as well.
+        final TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor)
+                .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack(
+                        WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */))
+                .build();
+        assertTrue(task2.getStack() instanceof PinnedActivityStack);
+        mStarter.updateBounds(task2, bounds);
+
+        verify(mService, times(1)).resizeStack(eq(task2.getStack().mStackId),
+                eq(bounds), anyBoolean(), anyBoolean(), anyBoolean(), anyInt());
+
+        // In the case of no animation, the stack and task bounds should be set immediately.
+        if (!ANIMATE) {
+            assertEquals(task2.getStack().getOverrideBounds(), bounds);
+            assertEquals(task2.getOverrideBounds(), bounds);
+        } else {
+            assertEquals(task2.getOverrideBounds(), new Rect());
+        }
+    }
+
+    @Test
+    public void testStartActivityPreconditions() throws Exception {
+        verifyStartActivityPreconditions(PRECONDITION_NO_CALLER_APP, START_PERMISSION_DENIED);
+        verifyStartActivityPreconditions(PRECONDITION_NO_INTENT_COMPONENT,
+                START_INTENT_NOT_RESOLVED);
+        verifyStartActivityPreconditions(PRECONDITION_NO_ACTIVITY_INFO, START_CLASS_NOT_FOUND);
+        verifyStartActivityPreconditions(PRECONDITION_SOURCE_PRESENT | PRECONDITION_REQUEST_CODE,
+                Intent.FLAG_ACTIVITY_FORWARD_RESULT, START_FORWARD_AND_REQUEST_CONFLICT);
+        verifyStartActivityPreconditions(
+                PRECONDITION_SOURCE_PRESENT | PRECONDITION_NO_VOICE_SESSION_SUPPORT
+                        | PRECONDITION_SOURCE_VOICE_SESSION | PRECONDITION_DIFFERENT_UID,
+                START_NOT_VOICE_COMPATIBLE);
+        verifyStartActivityPreconditions(
+                PRECONDITION_SOURCE_PRESENT | PRECONDITION_NO_VOICE_SESSION_SUPPORT
+                        | PRECONDITION_SOURCE_VOICE_SESSION | PRECONDITION_DIFFERENT_UID
+                        | PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION,
+                START_NOT_VOICE_COMPATIBLE);
+        verifyStartActivityPreconditions(PRECONDITION_CANNOT_START_ANY_ACTIVITY, START_ABORTED);
+        verifyStartActivityPreconditions(PRECONDITION_DISALLOW_APP_SWITCHING,
+                START_SWITCHES_CANCELED);
+    }
+
+    private static boolean containsConditions(int preconditions, int mask) {
+        return (preconditions & mask) == mask;
+    }
+
+    private void verifyStartActivityPreconditions(int preconditions, int expectedResult) {
+        verifyStartActivityPreconditions(preconditions, 0 /*launchFlags*/, expectedResult);
+    }
+
+    /**
+     * Excercises how the {@link ActivityStarter} reacts to various preconditions. The caller
+     * provides a bitmask of all the set conditions (such as {@link #PRECONDITION_NO_CALLER_APP})
+     * and the launch flags specified in the intent. The method constructs a call to
+     * {@link ActivityStarter#execute} based on these preconditions and ensures the result matches
+     * the expected. It is important to note that the method also checks side effects of the start,
+     * such as ensuring {@link ActivityOptions#abort()} is called in the relevant scenarios.
+     * @param preconditions A bitmask representing the preconditions for the launch
+     * @param launchFlags The launch flags to be provided by the launch {@link Intent}.
+     * @param expectedResult The expected result from the launch.
+     */
+    private void verifyStartActivityPreconditions(int preconditions, int launchFlags,
+            int expectedResult) {
+        final ActivityManagerService service = createActivityManagerService();
+        final IPackageManager packageManager = mock(IPackageManager.class);
+        final ActivityStartController controller = mock(ActivityStartController.class);
+
+        final ActivityStarter starter = new ActivityStarter(controller, service,
+                service.mStackSupervisor, mock(ActivityStartInterceptor.class));
+        final IApplicationThread caller = mock(IApplicationThread.class);
+
+        // If no caller app, return {@code null} {@link ProcessRecord}.
+        final ProcessRecord record = containsConditions(preconditions, PRECONDITION_NO_CALLER_APP)
+                ? null : new ProcessRecord(null, mock(BatteryStatsImpl.class),
+                mock(ApplicationInfo.class), null, 0);
+
+        doReturn(record).when(service).getRecordForAppLocked(anyObject());
+
+        final Intent intent = new Intent();
+        intent.setFlags(launchFlags);
+
+        final ActivityInfo aInfo = containsConditions(preconditions, PRECONDITION_NO_ACTIVITY_INFO)
+                ?  null : new ActivityInfo();
+
+        IVoiceInteractionSession voiceSession =
+                containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION)
+                ? mock(IVoiceInteractionSession.class) : null;
+
+        // Create source token
+        final ActivityBuilder builder = new ActivityBuilder(service).setTask(
+                new TaskBuilder(service.mStackSupervisor).setVoiceSession(voiceSession).build());
+
+        if (aInfo != null) {
+            aInfo.applicationInfo = new ApplicationInfo();
+            aInfo.applicationInfo.packageName =
+                    ActivityBuilder.getDefaultComponent().getPackageName();
+        }
+
+        // Offset uid by one from {@link ActivityInfo} to simulate different uids.
+        if (containsConditions(preconditions, PRECONDITION_DIFFERENT_UID)) {
+            builder.setUid(aInfo.applicationInfo.uid + 1);
+        }
+
+        final ActivityRecord source = builder.build();
+
+        if (!containsConditions(preconditions, PRECONDITION_NO_INTENT_COMPONENT)) {
+            intent.setComponent(source.realActivity);
+        }
+
+        if (containsConditions(preconditions, PRECONDITION_DISALLOW_APP_SWITCHING)) {
+            doReturn(false).when(service).checkAppSwitchAllowedLocked(anyInt(), anyInt(), anyInt(),
+                    anyInt(), any());
+        }
+
+        if (containsConditions(preconditions,PRECONDITION_CANNOT_START_ANY_ACTIVITY)) {
+            doReturn(false).when(service.mStackSupervisor).checkStartAnyActivityPermission(
+                    any(), any(), any(), anyInt(), anyInt(), anyInt(), any(),
+                    anyBoolean(), anyBoolean(), any(), any(), any());
+        }
+
+        try {
+            if (containsConditions(preconditions,
+                    PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION)) {
+                doAnswer((inv) -> {
+                    throw new RemoteException();
+                }).when(packageManager).activitySupportsIntent(eq(source.realActivity), eq(intent),
+                        any());
+            } else {
+                doReturn(!containsConditions(preconditions, PRECONDITION_NO_VOICE_SESSION_SUPPORT))
+                        .when(packageManager).activitySupportsIntent(eq(source.realActivity),
+                        eq(intent), any());
+            }
+        } catch (RemoteException e) {
+        }
+
+        final IBinder resultTo = containsConditions(preconditions, PRECONDITION_SOURCE_PRESENT)
+                || containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION)
+                ? source.appToken : null;
+
+        final int requestCode = containsConditions(preconditions, PRECONDITION_REQUEST_CODE)
+                ? 1 : 0;
+
+        final int result = starter.setCaller(caller)
+                .setIntent(intent)
+                .setActivityInfo(aInfo)
+                .setResultTo(resultTo)
+                .setRequestCode(requestCode)
+                .setReason("testLaunchActivityPermissionDenied")
+                .execute();
+
+        // In some cases the expected result internally is different than the published result. We
+        // must use ActivityStarter#getExternalResult to translate.
+        assertEquals(ActivityStarter.getExternalResult(expectedResult), result);
+
+        // Ensure that {@link ActivityOptions} are aborted with unsuccessful result.
+        if (expectedResult != START_SUCCESS) {
+            final ActivityStarter optionStarter = new ActivityStarter(mController, mService,
+                    mService.mStackSupervisor, mock(ActivityStartInterceptor.class));
+            final ActivityOptions options = spy(ActivityOptions.makeBasic());
+
+            final int optionResult = optionStarter.setCaller(caller)
+                    .setIntent(intent)
+                    .setActivityInfo(aInfo)
+                    .setResultTo(resultTo)
+                    .setRequestCode(requestCode)
+                    .setReason("testLaunchActivityPermissionDenied")
+                    .setActivityOptions(new SafeActivityOptions(options))
+                    .execute();
+            verify(options, times(1)).abort();
+        }
+    }
+
+    private ActivityStarter prepareStarter(int launchFlags) {
+        // always allow test to start activity.
+        doReturn(true).when(mService.mStackSupervisor).checkStartAnyActivityPermission(
+                any(), any(), any(), anyInt(), anyInt(), anyInt(), any(),
+                anyBoolean(), anyBoolean(), any(), any(), any());
+
+        // instrument the stack and task used.
+        final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
+                .setCreateStack(false)
+                .build();
+
+        // supervisor needs a focused stack.
+        mService.mStackSupervisor.mFocusedStack = stack;
+
+        // use factory that only returns spy task.
+        final TaskRecordFactory factory = mock(TaskRecordFactory.class);
+        TaskRecord.setTaskRecordFactory(factory);
+
+        // return task when created.
+        doReturn(task).when(factory).create(any(), anyInt(), any(), any(), any(), any());
+
+        // direct starter to use spy stack.
+        doReturn(stack).when(mService.mStackSupervisor)
+                .getLaunchStack(any(), any(), any(), anyBoolean());
+        doReturn(stack).when(mService.mStackSupervisor)
+                .getLaunchStack(any(), any(), any(), anyBoolean(), anyInt());
+
+        final Intent intent = new Intent();
+        intent.addFlags(launchFlags);
+        intent.setComponent(ActivityBuilder.getDefaultComponent());
+
+        final ActivityInfo info = new ActivityInfo();
+
+        info.applicationInfo = new ApplicationInfo();
+        info.applicationInfo.packageName = ActivityBuilder.getDefaultComponent().getPackageName();
+
+        return new ActivityStarter(mController, mService,
+                mService.mStackSupervisor, mock(ActivityStartInterceptor.class))
+                .setIntent(intent)
+                .setActivityInfo(info);
+    }
+
+    /**
+     * Ensures that values specified at launch time are passed to {@link LaunchParamsModifier}
+     * when we are laying out a new task.
+     */
+    @Test
+    public void testCreateTaskLayout() {
+        // modifier for validating passed values.
+        final LaunchParamsModifier modifier = mock(LaunchParamsModifier.class);
+        mService.mStackSupervisor.getLaunchParamsController().registerModifier(modifier);
+
+        // add custom values to activity info to make unique.
+        final ActivityInfo info = new ActivityInfo();
+        final Rect launchBounds = new Rect(0, 0, 20, 30);
+
+        final WindowLayout windowLayout =
+                new WindowLayout(10, .5f, 20, 1.0f, Gravity.NO_GRAVITY, 1, 1);
+
+        info.windowLayout = windowLayout;
+        info.applicationInfo = new ApplicationInfo();
+        info.applicationInfo.packageName = ActivityBuilder.getDefaultComponent().getPackageName();
+
+        // create starter.
+        final ActivityStarter optionStarter = prepareStarter(0 /* launchFlags */);
+
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchBounds(launchBounds);
+
+        // run starter.
+        optionStarter
+                .setReason("testCreateTaskLayout")
+                .setActivityInfo(info)
+                .setActivityOptions(new SafeActivityOptions(options))
+                .execute();
+
+        // verify that values are passed to the modifier.
+        verify(modifier, times(1)).onCalculate(any(), eq(windowLayout), any(), any(), eq(options),
+                any(), any());
+    }
+
+    /**
+     * This test ensures that if the intent is being delivered to a
+     */
+    @Test
+    public void testSplitScreenDeliverToTop() {
+        final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+
+        final ActivityRecord focusActivity = new ActivityBuilder(mService)
+                .setCreateTask(true)
+                .build();
+
+        focusActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        final ActivityRecord reusableActivity = new ActivityBuilder(mService)
+                .setCreateTask(true)
+                .build();
+
+        // Create reusable activity after entering split-screen so that it is the top secondary
+        // stack.
+        reusableActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+
+        // Set focus back to primary.
+        mService.mStackSupervisor.setFocusStackUnchecked("testSplitScreenDeliverToTop",
+                focusActivity.getStack());
+
+        doReturn(reusableActivity).when(mService.mStackSupervisor).findTaskLocked(any(), anyInt());
+
+        final int result = starter.setReason("testSplitScreenDeliverToTop").execute();
+
+        // Ensure result is delivering intent to top.
+        assertEquals(result, START_DELIVERED_TO_TOP);
+    }
+
+    /**
+     * This test ensures that if the intent is being delivered to a split-screen unfocused task
+     * reports it is brought to front instead of delivering to top.
+     */
+    @Test
+    public void testSplitScreenTaskToFront() {
+        final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+
+        // Create reusable activity here first. Setting the windowing mode of the primary stack
+        // will move the existing standard full screen stack to secondary, putting this one on the
+        // bottom.
+        final ActivityRecord reusableActivity = new ActivityBuilder(mService)
+                .setCreateTask(true)
+                .build();
+
+        reusableActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+
+        final ActivityRecord focusActivity = new ActivityBuilder(mService)
+                .setCreateTask(true)
+                .build();
+
+        // Enter split-screen. Primary stack should have focus.
+        focusActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        doReturn(reusableActivity).when(mService.mStackSupervisor).findTaskLocked(any(), anyInt());
+
+        final int result = starter.setReason("testSplitScreenMoveToFront").execute();
+
+        // Ensure result is moving task to front.
+        assertEquals(result, START_TASK_TO_FRONT);
+    }
+
+    /**
+     * Tests activity is cleaned up properly in a task mode violation.
+     */
+    @Test
+    public void testTaskModeViolation() {
+        final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+        assertNoTasks(display);
+
+        final ActivityStarter starter = prepareStarter(0);
+
+        final LockTaskController lockTaskController = mService.getLockTaskController();
+        doReturn(true).when(lockTaskController).isLockTaskModeViolation(any());
+
+        final int result = starter.setReason("testTaskModeViolation").execute();
+
+        assertEquals(START_RETURN_LOCK_TASK_MODE_VIOLATION, result);
+        assertNoTasks(display);
+    }
+
+    private void assertNoTasks(ActivityDisplay display) {
+        for (int i = display.getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = display.getChildAt(i);
+            assertTrue(stack.getAllTasks().isEmpty());
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
new file mode 100644
index 0000000..1cd111f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 2017 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.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+
+import android.app.ActivityOptions;
+import com.android.server.wm.DisplayWindowController;
+
+import org.junit.Rule;
+import org.mockito.invocation.InvocationOnMock;
+
+import android.app.IApplicationThread;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.service.voice.IVoiceInteractionSession;
+import android.support.test.InstrumentationRegistry;
+import android.testing.DexmakerShareClassLoaderRule;
+
+
+import com.android.internal.app.IVoiceInteractor;
+
+import com.android.server.AttributeCache;
+import com.android.server.wm.AppWindowContainerController;
+import com.android.server.wm.PinnedStackWindowController;
+import com.android.server.wm.StackWindowController;
+import com.android.server.wm.TaskWindowContainerController;
+import com.android.server.wm.WindowManagerService;
+import com.android.server.wm.WindowTestUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.MockitoAnnotations;
+
+
+/**
+ * A base class to handle common operations in activity related unit tests.
+ */
+public class ActivityTestsBase {
+    private static boolean sOneTimeSetupDone = false;
+
+    @Rule
+    public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+            new DexmakerShareClassLoaderRule();
+
+    private final Context mContext = InstrumentationRegistry.getContext();
+    private HandlerThread mHandlerThread;
+
+    // Default package name
+    static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo";
+
+    // Default base activity name
+    private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity";
+
+    @Before
+    public void setUp() throws Exception {
+        if (!sOneTimeSetupDone) {
+            sOneTimeSetupDone = true;
+            MockitoAnnotations.initMocks(this);
+        }
+        mHandlerThread = new HandlerThread("ActivityTestsBaseThread");
+        mHandlerThread.start();
+    }
+
+    @After
+    public void tearDown() {
+        mHandlerThread.quitSafely();
+    }
+
+    protected ActivityManagerService createActivityManagerService() {
+        final ActivityManagerService service =
+                setupActivityManagerService(new TestActivityManagerService(mContext));
+        AttributeCache.init(mContext);
+        return service;
+    }
+
+    protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) {
+        service = spy(service);
+        doReturn(mock(IPackageManager.class)).when(service).getPackageManager();
+        doNothing().when(service).grantEphemeralAccessLocked(anyInt(), any(), anyInt(), anyInt());
+        service.mWindowManager = prepareMockWindowManager();
+        return service;
+    }
+
+    /**
+     * Builder for creating new activities.
+     */
+    protected static class ActivityBuilder {
+        // An id appended to the end of the component name to make it unique
+        private static int sCurrentActivityId = 0;
+
+
+
+        private final ActivityManagerService mService;
+
+        private ComponentName mComponent;
+        private TaskRecord mTaskRecord;
+        private int mUid;
+        private boolean mCreateTask;
+        private ActivityStack mStack;
+        private int mActivityFlags;
+
+        ActivityBuilder(ActivityManagerService service) {
+            mService = service;
+        }
+
+        ActivityBuilder setComponent(ComponentName component) {
+            mComponent = component;
+            return this;
+        }
+
+        static ComponentName getDefaultComponent() {
+            return ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+                    DEFAULT_COMPONENT_PACKAGE_NAME);
+        }
+
+        ActivityBuilder setTask(TaskRecord task) {
+            mTaskRecord = task;
+            return this;
+        }
+
+        ActivityBuilder setActivityFlags(int flags) {
+            mActivityFlags = flags;
+            return this;
+        }
+
+        ActivityBuilder setStack(ActivityStack stack) {
+            mStack = stack;
+            return this;
+        }
+
+        ActivityBuilder setCreateTask(boolean createTask) {
+            mCreateTask = createTask;
+            return this;
+        }
+
+        ActivityBuilder setUid(int uid) {
+            mUid = uid;
+            return this;
+        }
+
+        ActivityRecord build() {
+            if (mComponent == null) {
+                final int id = sCurrentActivityId++;
+                mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+                        DEFAULT_COMPONENT_CLASS_NAME + id);
+            }
+
+            if (mCreateTask) {
+                mTaskRecord = new TaskBuilder(mService.mStackSupervisor)
+                        .setComponent(mComponent)
+                        .setStack(mStack).build();
+            }
+
+            Intent intent = new Intent();
+            intent.setComponent(mComponent);
+            final ActivityInfo aInfo = new ActivityInfo();
+            aInfo.applicationInfo = new ApplicationInfo();
+            aInfo.applicationInfo.packageName = mComponent.getPackageName();
+            aInfo.applicationInfo.uid = mUid;
+            aInfo.flags |= mActivityFlags;
+
+            final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
+                    0 /* launchedFromPid */, 0, null, intent, null,
+                    aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,
+                    0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */,
+                    mService.mStackSupervisor, null /* options */, null /* sourceRecord */);
+            activity.mWindowContainerController = mock(AppWindowContainerController.class);
+
+            if (mTaskRecord != null) {
+                mTaskRecord.addActivityToTop(activity);
+            }
+
+            activity.setProcess(new ProcessRecord(null, null,
+                    mService.mContext.getApplicationInfo(), "name", 12345));
+            activity.app.thread = mock(IApplicationThread.class);
+
+            return activity;
+        }
+    }
+
+    /**
+     * Builder for creating new tasks.
+     */
+    protected static class TaskBuilder {
+        // Default package name
+        static final String DEFAULT_PACKAGE = "com.bar";
+
+        private final ActivityStackSupervisor mSupervisor;
+
+        private ComponentName mComponent;
+        private String mPackage;
+        private int mFlags = 0;
+        private int mTaskId = 0;
+        private int mUserId = 0;
+        private IVoiceInteractionSession mVoiceSession;
+        private boolean mCreateStack = true;
+
+        private ActivityStack mStack;
+
+        TaskBuilder(ActivityStackSupervisor supervisor) {
+            mSupervisor = supervisor;
+        }
+
+        TaskBuilder setComponent(ComponentName component) {
+            mComponent = component;
+            return this;
+        }
+
+        TaskBuilder setPackage(String packageName) {
+            mPackage = packageName;
+            return this;
+        }
+
+        /**
+         * Set to {@code true} by default, set to {@code false} to prevent the task from
+         * automatically creating a parent stack.
+         */
+        TaskBuilder setCreateStack(boolean createStack) {
+            mCreateStack = createStack;
+            return this;
+        }
+
+        TaskBuilder setVoiceSession(IVoiceInteractionSession session) {
+            mVoiceSession = session;
+            return this;
+        }
+
+        TaskBuilder setFlags(int flags) {
+            mFlags = flags;
+            return this;
+        }
+
+        TaskBuilder setTaskId(int taskId) {
+            mTaskId = taskId;
+            return this;
+        }
+
+        TaskBuilder setUserId(int userId) {
+            mUserId = userId;
+            return this;
+        }
+
+        TaskBuilder setStack(ActivityStack stack) {
+            mStack = stack;
+            return this;
+        }
+
+        TaskRecord build() {
+            if (mStack == null && mCreateStack) {
+                mStack = mSupervisor.getDefaultDisplay().createStack(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+            }
+
+            final ActivityInfo aInfo = new ActivityInfo();
+            aInfo.applicationInfo = new ApplicationInfo();
+            aInfo.applicationInfo.packageName = mPackage;
+
+            Intent intent = new Intent();
+            if (mComponent == null) {
+                mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+                        DEFAULT_COMPONENT_CLASS_NAME);
+            }
+
+            intent.setComponent(mComponent);
+            intent.setFlags(mFlags);
+
+            final TestTaskRecord task = new TestTaskRecord(mSupervisor.mService, mTaskId, aInfo,
+                    intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/);
+            task.userId = mUserId;
+
+            if (mStack != null) {
+                mSupervisor.setFocusStackUnchecked("test", mStack);
+                mStack.addTask(task, true, "creating test task");
+                task.setStack(mStack);
+                task.setWindowContainerController();
+            }
+
+            task.touchActiveTime();
+
+            return task;
+        }
+
+        private static class TestTaskRecord extends TaskRecord {
+            TestTaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info,
+                       Intent _intent, IVoiceInteractionSession _voiceSession,
+                       IVoiceInteractor _voiceInteractor) {
+                super(service, _taskId, info, _intent, _voiceSession, _voiceInteractor);
+            }
+
+            @Override
+            void createWindowContainer(boolean onTop, boolean showForAllUsers) {
+                setWindowContainerController();
+            }
+
+            private void setWindowContainerController() {
+                setWindowContainerController(mock(TaskWindowContainerController.class));
+            }
+        }
+    }
+
+    /**
+     * An {@link ActivityManagerService} subclass which provides a test
+     * {@link ActivityStackSupervisor}.
+     */
+    protected static class TestActivityManagerService extends ActivityManagerService {
+        private ClientLifecycleManager mLifecycleManager;
+        private LockTaskController mLockTaskController;
+
+        TestActivityManagerService(Context context) {
+            super(context);
+            mSupportsMultiWindow = true;
+            mSupportsMultiDisplay = true;
+            mSupportsSplitScreenMultiWindow = true;
+            mSupportsFreeformWindowManagement = true;
+            mSupportsPictureInPicture = true;
+            mWindowManager = WindowTestUtils.getMockWindowManagerService();
+        }
+
+        @Override
+        public ClientLifecycleManager getLifecycleManager() {
+            if (mLifecycleManager == null) {
+                return super.getLifecycleManager();
+            }
+            return mLifecycleManager;
+        }
+
+        public LockTaskController getLockTaskController() {
+            if (mLockTaskController == null) {
+                mLockTaskController = spy(super.getLockTaskController());
+            }
+
+            return mLockTaskController;
+        }
+
+        void setLifecycleManager(ClientLifecycleManager manager) {
+            mLifecycleManager = manager;
+        }
+
+        @Override
+        final protected ActivityStackSupervisor createStackSupervisor() {
+            final ActivityStackSupervisor supervisor = spy(createTestSupervisor());
+            final KeyguardController keyguardController = mock(KeyguardController.class);
+
+            // No home stack is set.
+            doNothing().when(supervisor).moveHomeStackToFront(any());
+            doReturn(true).when(supervisor).moveHomeStackTaskToTop(any());
+            // Invoked during {@link ActivityStack} creation.
+            doNothing().when(supervisor).updateUIDsPresentOnDisplay();
+            // Always keep things awake.
+            doReturn(true).when(supervisor).hasAwakeDisplay();
+            // Called when moving activity to pinned stack.
+            doNothing().when(supervisor).ensureActivitiesVisibleLocked(any(), anyInt(), anyBoolean());
+            // Do not schedule idle timeouts
+            doNothing().when(supervisor).scheduleIdleTimeoutLocked(any());
+            // unit test version does not handle launch wake lock
+            doNothing().when(supervisor).acquireLaunchWakelock();
+            doReturn(keyguardController).when(supervisor).getKeyguardController();
+
+            supervisor.initialize();
+
+            return supervisor;
+        }
+
+        protected ActivityStackSupervisor createTestSupervisor() {
+            return new TestActivityStackSupervisor(this, mHandlerThread.getLooper());
+        }
+
+        @Override
+        void updateUsageStats(ActivityRecord component, boolean resumed) {
+        }
+    }
+
+    /**
+     * An {@link ActivityStackSupervisor} which stubs out certain methods that depend on
+     * setup not available in the test environment. Also specifies an injector for
+     */
+    protected static class TestActivityStackSupervisor extends ActivityStackSupervisor {
+        private ActivityDisplay mDisplay;
+        private KeyguardController mKeyguardController;
+
+        public TestActivityStackSupervisor(ActivityManagerService service, Looper looper) {
+            super(service, looper);
+            mDisplayManager =
+                    (DisplayManager) mService.mContext.getSystemService(Context.DISPLAY_SERVICE);
+            mWindowManager = prepareMockWindowManager();
+            mKeyguardController = mock(KeyguardController.class);
+        }
+
+        @Override
+        public void initialize() {
+            super.initialize();
+            mDisplay = spy(new TestActivityDisplay(this, DEFAULT_DISPLAY));
+            attachDisplay(mDisplay);
+        }
+
+        @Override
+        public KeyguardController getKeyguardController() {
+            return mKeyguardController;
+        }
+
+        @Override
+        ActivityDisplay getDefaultDisplay() {
+            return mDisplay;
+        }
+
+        // Just return the current front task. This is called internally so we cannot use spy to mock this out.
+        @Override
+        ActivityStack getNextFocusableStackLocked(ActivityStack currentFocus,
+                boolean ignoreCurrent) {
+            return mFocusedStack;
+        }
+    }
+
+    protected static class TestActivityDisplay extends ActivityDisplay {
+
+        private final ActivityStackSupervisor mSupervisor;
+        TestActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
+            super(supervisor, displayId);
+            mSupervisor = supervisor;
+        }
+
+        @Override
+        <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
+                int stackId, boolean onTop) {
+            if (windowingMode == WINDOWING_MODE_PINNED) {
+                return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop) {
+                    @Override
+                    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
+                        return new Rect(50, 50, 100, 100);
+                    }
+
+                    @Override
+                    PinnedStackWindowController createStackWindowController(int displayId,
+                            boolean onTop, Rect outBounds) {
+                        return mock(PinnedStackWindowController.class);
+                    }
+                };
+            } else {
+                return (T) new TestActivityStack(
+                        this, stackId, mSupervisor, windowingMode, activityType, onTop);
+            }
+        }
+
+        @Override
+        protected DisplayWindowController createWindowContainerController() {
+            return mock(DisplayWindowController.class);
+        }
+    }
+
+    private static WindowManagerService prepareMockWindowManager() {
+        final WindowManagerService service = WindowTestUtils.getMockWindowManagerService();
+
+        doAnswer((InvocationOnMock invocationOnMock) -> {
+            final Runnable runnable = invocationOnMock.<Runnable>getArgument(0);
+            if (runnable != null) {
+                runnable.run();
+            }
+            return null;
+        }).when(service).inSurfaceTransaction(any());
+
+        return service;
+    }
+
+    /**
+     * Overridden {@link ActivityStack} that tracks test metrics, such as the number of times a
+     * method is called. Note that its functionality depends on the implementations of the
+     * construction arguments.
+     */
+    protected static class TestActivityStack<T extends StackWindowController>
+            extends ActivityStack<T> {
+        private int mOnActivityRemovedFromStackCount = 0;
+        private T mContainerController;
+
+        static final int IS_TRANSLUCENT_UNSET = 0;
+        static final int IS_TRANSLUCENT_FALSE = 1;
+        static final int IS_TRANSLUCENT_TRUE = 2;
+        private int mIsTranslucent = IS_TRANSLUCENT_UNSET;
+
+        static final int SUPPORTS_SPLIT_SCREEN_UNSET = 0;
+        static final int SUPPORTS_SPLIT_SCREEN_FALSE = 1;
+        static final int SUPPORTS_SPLIT_SCREEN_TRUE = 2;
+        private int mSupportsSplitScreen = SUPPORTS_SPLIT_SCREEN_UNSET;
+
+        TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
+                int windowingMode, int activityType, boolean onTop) {
+            super(display, stackId, supervisor, windowingMode, activityType, onTop);
+        }
+
+        @Override
+        void onActivityRemovedFromStack(ActivityRecord r) {
+            mOnActivityRemovedFromStackCount++;
+            super.onActivityRemovedFromStack(r);
+        }
+
+        // Returns the number of times {@link #onActivityRemovedFromStack} has been called
+        int onActivityRemovedFromStackInvocationCount() {
+            return mOnActivityRemovedFromStackCount;
+        }
+
+        @Override
+        protected T createStackWindowController(int displayId, boolean onTop, Rect outBounds) {
+            mContainerController = (T) WindowTestUtils.createMockStackWindowContainerController();
+
+            // Primary pinned stacks require a non-empty out bounds to be set or else all tasks
+            // will be moved to the full screen stack.
+            if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                outBounds.set(0, 0, 100, 100);
+            }
+            return mContainerController;
+        }
+
+        @Override
+        T getWindowContainerController() {
+            return mContainerController;
+        }
+
+        void setIsTranslucent(boolean isTranslucent) {
+            mIsTranslucent = isTranslucent ? IS_TRANSLUCENT_TRUE : IS_TRANSLUCENT_FALSE;
+        }
+
+        @Override
+        boolean isStackTranslucent(ActivityRecord starting) {
+            switch (mIsTranslucent) {
+                case IS_TRANSLUCENT_TRUE:
+                    return true;
+                case IS_TRANSLUCENT_FALSE:
+                    return false;
+                case IS_TRANSLUCENT_UNSET:
+                default:
+                    return super.isStackTranslucent(starting);
+            }
+        }
+
+        void setSupportsSplitScreen(boolean supportsSplitScreen) {
+            mSupportsSplitScreen = supportsSplitScreen
+                    ? SUPPORTS_SPLIT_SCREEN_TRUE : SUPPORTS_SPLIT_SCREEN_FALSE;
+        }
+
+        @Override
+        public boolean supportsSplitScreenWindowingMode() {
+            switch (mSupportsSplitScreen) {
+                case SUPPORTS_SPLIT_SCREEN_TRUE:
+                    return true;
+                case SUPPORTS_SPLIT_SCREEN_FALSE:
+                    return false;
+                case SUPPORTS_SPLIT_SCREEN_UNSET:
+                default:
+                    return super.supportsSplitScreenWindowingMode();
+            }
+        }
+
+        @Override
+        void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
+                                 boolean newTask, boolean keepCurTransition,
+                                 ActivityOptions options) {
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
new file mode 100644
index 0000000..3d11c4c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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 android.content.Context;
+import android.os.Handler;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.AppOpsService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * runtest -c com.android.server.am.AppErrorDialogTest frameworks-services
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AppErrorDialogTest {
+
+    private Context mContext;
+    private ActivityManagerService mService;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mService = new ActivityManagerService(new ActivityManagerService.Injector() {
+            @Override
+            public AppOpsService getAppOpsService(File file, Handler handler) {
+                return null;
+            }
+
+            @Override
+            public Handler getUiHandler(ActivityManagerService service) {
+                return null;
+            }
+
+            @Override
+            public boolean isNetworkRestrictedForUid(int uid) {
+                return false;
+            }
+        });
+    }
+
+    @Test
+    @UiThreadTest
+    public void testCreateWorks() throws Exception {
+        AppErrorDialog.Data data = new AppErrorDialog.Data();
+        data.proc = new ProcessRecord(null, null, mContext.getApplicationInfo(), "name", 12345);
+        data.result = new AppErrorResult();
+
+        AppErrorDialog dialog = new AppErrorDialog(mContext, mService, data);
+
+        dialog.create();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java b/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
new file mode 100644
index 0000000..ce88d84
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2017 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.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.graphics.Bitmap.Config.ARGB_8888;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+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.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.view.IWindowManager;
+
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Note: Currently, we only support fetching the screenshot for the current application, so the
+ * screenshot checks are hardcoded accordingly.
+ *
+ * runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AssistDataRequesterTest extends ActivityTestsBase {
+
+    private static final String TAG = AssistDataRequesterTest.class.getSimpleName();
+
+    private static final boolean CURRENT_ACTIVITY_ASSIST_ALLOWED = true;
+    private static final boolean CALLER_ASSIST_STRUCTURE_ALLOWED = true;
+    private static final boolean CALLER_ASSIST_SCREENSHOT_ALLOWED = true;
+    private static final boolean FETCH_DATA = true;
+    private static final boolean FETCH_SCREENSHOTS = true;
+    private static final boolean ALLOW_FETCH_DATA = true;
+    private static final boolean ALLOW_FETCH_SCREENSHOTS = true;
+
+    private static final int TEST_UID = 0;
+    private static final String TEST_PACKAGE = "";
+
+    private Context mContext;
+    private AssistDataRequester mDataRequester;
+    private Callbacks mCallbacks;
+    private Object mCallbacksLock;
+    private Handler mHandler;
+    private IActivityManager mAm;
+    private IWindowManager mWm;
+    private AppOpsManager mAppOpsManager;
+
+    /**
+     * The requests to fetch assist data are done incrementally from the text thread, and we
+     * immediately post onto the main thread handler below, which would immediately make the
+     * callback and decrement the pending counts. In order to assert the pending counts, we defer
+     * the callbacks on the test-side until after we flip the gate, after which we can drain the
+     * main thread handler and make assertions on the actual callbacks
+     */
+    private CountDownLatch mGate;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mAm = mock(IActivityManager.class);
+        mWm = mock(IWindowManager.class);
+        mAppOpsManager = mock(AppOpsManager.class);
+        mContext =  InstrumentationRegistry.getContext();
+        mHandler = new Handler(Looper.getMainLooper());
+        mCallbacksLock = new Object();
+        mCallbacks = new Callbacks();
+        mDataRequester = new AssistDataRequester(mContext, mAm, mWm, mAppOpsManager, mCallbacks,
+                mCallbacksLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
+
+        // Gate the continuation of the assist data callbacks until we are ready within the tests
+        mGate = new CountDownLatch(1);
+        doAnswer(invocation -> {
+            mHandler.post(() -> {
+                try {
+                    mGate.await(10, TimeUnit.SECONDS);
+                    mDataRequester.onHandleAssistData(new Bundle());
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Failed to wait", e);
+                }
+            });
+            return true;
+        }).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(), anyBoolean(),
+                anyBoolean());
+        doAnswer(invocation -> {
+            mHandler.post(() -> {
+                try {
+                    mGate.await(10, TimeUnit.SECONDS);
+                    mDataRequester.onHandleAssistScreenshot(Bitmap.createBitmap(1, 1, ARGB_8888));
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Failed to wait", e);
+                }
+            });
+            return true;
+        }).when(mWm).requestAssistScreenshot(any());
+    }
+
+    private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed,
+            boolean assistScreenshotAllowed) throws Exception {
+        doReturn(currentActivityAssistAllowed).when(mAm).isAssistDataAllowedOnCurrentActivity();
+        doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
+                .checkOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString());
+        doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
+                .checkOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString());
+    }
+
+    @Test
+    public void testRequestData() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(5, 5, 1, 1);
+    }
+
+    @Test
+    public void testEmptyActivities_expectNoCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(0), FETCH_DATA, FETCH_SCREENSHOTS,
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(0, 0, 0, 0);
+    }
+
+    @Test
+    public void testCurrentAppDisallow_expectNullCallbacks() throws Exception {
+        setupMocks(!CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(0, 1, 0, 1);
+    }
+
+    @Test
+    public void testProcessPendingData() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mCallbacks.canHandleReceivedData = false;
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+        assertTrue(mDataRequester.getPendingDataCount() == 5);
+        assertTrue(mDataRequester.getPendingScreenshotCount() == 1);
+        mGate.countDown();
+        waitForIdle(mHandler);
+
+        // Callbacks still not ready to receive, but all pending data is received
+        assertTrue(mDataRequester.getPendingDataCount() == 0);
+        assertTrue(mDataRequester.getPendingScreenshotCount() == 0);
+        assertTrue(mCallbacks.receivedData.isEmpty());
+        assertTrue(mCallbacks.receivedScreenshots.isEmpty());
+        assertFalse(mCallbacks.requestCompleted);
+
+        mCallbacks.canHandleReceivedData = true;
+        mDataRequester.processPendingAssistData();
+        // Since we are posting the callback for the request-complete, flush the handler as well
+        mGate.countDown();
+        waitForIdle(mHandler);
+        assertTrue(mCallbacks.receivedData.size() == 5);
+        assertTrue(mCallbacks.receivedScreenshots.size() == 1);
+        assertTrue(mCallbacks.requestCompleted);
+
+        // Clear the state and ensure that we only process pending data once
+        mCallbacks.reset();
+        mDataRequester.processPendingAssistData();
+        assertTrue(mCallbacks.receivedData.isEmpty());
+        assertTrue(mCallbacks.receivedScreenshots.isEmpty());
+    }
+
+    @Test
+    public void testNoFetchData_expectNoDataCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), !FETCH_DATA, FETCH_SCREENSHOTS,
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(0, 0, 0, 1);
+    }
+
+    @Test
+    public void testDisallowAssistStructure_expectNullDataCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, !CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+        // Expect a single null data when the appops is denied
+        assertReceivedDataCount(0, 1, 0, 1);
+    }
+
+    @Test
+    public void testDisallowAssistContextExtras_expectNullDataCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+        doReturn(false).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(),
+                anyBoolean(), anyBoolean());
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+        // Expect a single null data when requestAssistContextExtras() fails
+        assertReceivedDataCount(0, 1, 0, 1);
+    }
+
+    @Test
+    public void testNoFetchScreenshots_expectNoScreenshotCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, !FETCH_SCREENSHOTS,
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(5, 5, 0, 0);
+    }
+
+    @Test
+    public void testDisallowAssistScreenshot_expectNullScreenshotCallback() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                !CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+        // Expect a single null screenshot when the appops is denied
+        assertReceivedDataCount(5, 5, 0, 1);
+    }
+
+    @Test
+    public void testCanNotHandleReceivedData_expectNoCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, !CALLER_ASSIST_STRUCTURE_ALLOWED,
+                !CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mCallbacks.canHandleReceivedData = false;
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+        mGate.countDown();
+        waitForIdle(mHandler);
+        assertTrue(mCallbacks.receivedData.isEmpty());
+        assertTrue(mCallbacks.receivedScreenshots.isEmpty());
+    }
+
+    @Test
+    public void testRequestDataNoneAllowed_expectNullCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                !ALLOW_FETCH_DATA, !ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(0, 1, 0, 1);
+    }
+
+    private void assertReceivedDataCount(int numPendingData, int numReceivedData,
+            int numPendingScreenshots, int numReceivedScreenshots) throws Exception {
+        assertTrue("Expected " + numPendingData + " pending data, got "
+                        + mDataRequester.getPendingDataCount(),
+                mDataRequester.getPendingDataCount() == numPendingData);
+        assertTrue("Expected " + numPendingScreenshots + " pending screenshots, got "
+                        + mDataRequester.getPendingScreenshotCount(),
+                mDataRequester.getPendingScreenshotCount() == numPendingScreenshots);
+        assertFalse("Expected request NOT completed", mCallbacks.requestCompleted);
+        mGate.countDown();
+        waitForIdle(mHandler);
+        assertTrue("Expected " + numReceivedData + " data, received "
+                        + mCallbacks.receivedData.size(),
+                mCallbacks.receivedData.size() == numReceivedData);
+        assertTrue("Expected " + numReceivedScreenshots + " screenshots, received "
+                        + mCallbacks.receivedScreenshots.size(),
+                mCallbacks.receivedScreenshots.size() == numReceivedScreenshots);
+        assertTrue("Expected request completed", mCallbacks.requestCompleted);
+    }
+
+    private List<IBinder> createActivityList(int size) {
+        ArrayList<IBinder> activities = new ArrayList<>();
+        for (int i = 0; i < size; i++) {
+            activities.add(mock(IBinder.class));
+        }
+        return activities;
+    }
+
+    public void waitForIdle(Handler h) throws Exception {
+        if (Looper.myLooper() == h.getLooper()) {
+            throw new RuntimeException("This method can not be called from the waiting looper");
+        }
+        CountDownLatch latch = new CountDownLatch(1);
+        h.post(() -> latch.countDown());
+        latch.await(2, TimeUnit.SECONDS);
+    }
+
+    private class Callbacks implements AssistDataRequesterCallbacks {
+
+        boolean canHandleReceivedData = true;
+        boolean requestCompleted = false;
+        ArrayList<Bundle> receivedData = new ArrayList<>();
+        ArrayList<Bitmap> receivedScreenshots = new ArrayList<>();
+
+        void reset() {
+            canHandleReceivedData = true;
+            receivedData.clear();
+            receivedScreenshots.clear();
+        }
+
+        @Override
+        public boolean canHandleReceivedAssistDataLocked() {
+            return canHandleReceivedData;
+        }
+
+        @Override
+        public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+            receivedData.add(data);
+        }
+
+        @Override
+        public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+            receivedScreenshots.add(screenshot);
+        }
+
+        @Override
+        public void onAssistRequestCompleted() {
+            mHandler.post(() -> {
+                try {
+                    mGate.await(10, TimeUnit.SECONDS);
+                    requestCompleted = true;
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Failed to wait", e);
+                }
+            });
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java b/services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java
new file mode 100644
index 0000000..ef6d5e8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java
@@ -0,0 +1,44 @@
+package com.android.server.am;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.IApplicationThread;
+import android.app.servertransaction.ClientTransaction;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class ClientLifecycleManagerTests {
+
+    @Test
+    public void testScheduleAndRecycleBinderClientTransaction() throws Exception {
+        ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.class),
+                new Binder()));
+
+        ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager();
+        clientLifecycleManager.scheduleTransaction(item);
+
+        verify(item, times(1)).recycle();
+    }
+
+    @Test
+    public void testScheduleNoRecycleNonBinderClientTransaction() throws Exception {
+        ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.Stub.class),
+                new Binder()));
+
+        ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager();
+        clientLifecycleManager.scheduleTransaction(item);
+
+        verify(item, times(0)).recycle();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
new file mode 100644
index 0000000..da30c11
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2017 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 com.android.server.am.ActivityManagerService.Injector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContentResolver;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.AppOpsService;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+
+/**
+ * Test class for {@link CoreSettingsObserver}.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.server.am.CoreSettingsObserverTest frameworks-services
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksServicesTests
+ * Install: adb install -r \
+ *     ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -e class com.android.server.am.CoreSettingsObserverTest -w \
+ *     com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CoreSettingsObserverTest {
+    private static final String TEST_SETTING_SECURE_INT = "secureInt";
+    private static final String TEST_SETTING_GLOBAL_FLOAT = "globalFloat";
+    private static final String TEST_SETTING_SYSTEM_STRING = "systemString";
+
+    private static final int TEST_INT = 111;
+    private static final float TEST_FLOAT = 3.14f;
+    private static final String TEST_STRING = "testString";
+
+    private ActivityManagerService mAms;
+    @Mock private Context mContext;
+
+    private MockContentResolver mContentResolver;
+    private CoreSettingsObserver mCoreSettingsObserver;
+
+    @BeforeClass
+    public static void setupOnce() {
+        FakeSettingsProvider.clearSettingsProvider();
+        CoreSettingsObserver.sSecureSettingToTypeMap.put(TEST_SETTING_SECURE_INT, int.class);
+        CoreSettingsObserver.sGlobalSettingToTypeMap.put(TEST_SETTING_GLOBAL_FLOAT, float.class);
+        CoreSettingsObserver.sSystemSettingToTypeMap.put(TEST_SETTING_SYSTEM_STRING, String.class);
+    }
+
+    @AfterClass
+    public static void tearDownOnce() {
+        FakeSettingsProvider.clearSettingsProvider();
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        final Context originalContext = InstrumentationRegistry.getContext();
+        when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo());
+        mContentResolver = new MockContentResolver(mContext);
+        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+
+        mAms = new ActivityManagerService(new TestInjector());
+        mCoreSettingsObserver = new CoreSettingsObserver(mAms);
+    }
+
+    @Test
+    public void testPopulateSettings() {
+        Settings.Secure.putInt(mContentResolver, TEST_SETTING_SECURE_INT, TEST_INT);
+        Settings.Global.putFloat(mContentResolver, TEST_SETTING_GLOBAL_FLOAT, TEST_FLOAT);
+        Settings.System.putString(mContentResolver, TEST_SETTING_SYSTEM_STRING, TEST_STRING);
+
+        final Bundle settingsBundle = getPopulatedBundle();
+
+        assertEquals("Unexpected value of " + TEST_SETTING_SECURE_INT,
+                TEST_INT, settingsBundle.getInt(TEST_SETTING_SECURE_INT));
+        assertEquals("Unexpected value of " + TEST_SETTING_GLOBAL_FLOAT,
+                TEST_FLOAT, settingsBundle.getFloat(TEST_SETTING_GLOBAL_FLOAT), 0);
+        assertEquals("Unexpected value of " + TEST_SETTING_SYSTEM_STRING,
+                TEST_STRING, settingsBundle.getString(TEST_SETTING_SYSTEM_STRING));
+    }
+
+    @Test
+    public void testPopulateSettings_settingNotSet() {
+        final Bundle settingsBundle = getPopulatedBundle();
+
+        assertFalse("Bundle should not contain " + TEST_SETTING_SECURE_INT,
+                settingsBundle.containsKey(TEST_SETTING_SECURE_INT));
+        assertFalse("Bundle should not contain " + TEST_SETTING_GLOBAL_FLOAT,
+                settingsBundle.containsKey(TEST_SETTING_GLOBAL_FLOAT));
+        assertFalse("Bundle should not contain " + TEST_SETTING_SYSTEM_STRING,
+                settingsBundle.containsKey(TEST_SETTING_SYSTEM_STRING));
+    }
+
+    private Bundle getPopulatedBundle() {
+        final Bundle settingsBundle = new Bundle();
+        mCoreSettingsObserver.populateSettings(settingsBundle,
+                CoreSettingsObserver.sGlobalSettingToTypeMap);
+        mCoreSettingsObserver.populateSettings(settingsBundle,
+                CoreSettingsObserver.sSecureSettingToTypeMap);
+        mCoreSettingsObserver.populateSettings(settingsBundle,
+                CoreSettingsObserver.sSystemSettingToTypeMap);
+        return settingsBundle;
+    }
+
+    private class TestInjector extends Injector {
+        @Override
+        public Context getContext() {
+            return mContext;
+        }
+
+        public AppOpsService getAppOpsService(File file, Handler handler) {
+            return null;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return null;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java b/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java
new file mode 100644
index 0000000..d9b3e1c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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 android.content.ContentResolver;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContentResolver;
+
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests for {@link GlobalSettingsToPropertiesMapper}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GlobalSettingsToPropertiesMapperTest {
+    private static final String[][] TEST_MAPPING = new String[][] {
+        {Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "TestProperty"}
+    };
+
+    private TestMapper mTestMapper;
+    private MockContentResolver mMockContentResolver;
+
+    @Before
+    public void setup() {
+        // Use FakeSettingsProvider to not affect global state
+        mMockContentResolver = new MockContentResolver(InstrumentationRegistry.getContext());
+        mMockContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        mTestMapper = new TestMapper(mMockContentResolver);
+    }
+
+    @Test
+    public void testUpdatePropertiesFromGlobalSettings() {
+        Settings.Global.putString(mMockContentResolver,
+                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue");
+
+        mTestMapper.updatePropertiesFromGlobalSettings();
+        String propValue = mTestMapper.systemPropertiesGet("TestProperty");
+        Assert.assertEquals("testValue", propValue);
+
+        Settings.Global.putString(mMockContentResolver,
+                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue2");
+        mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
+                "TestProperty");
+        propValue = mTestMapper.systemPropertiesGet("TestProperty");
+        Assert.assertEquals("testValue2", propValue);
+
+        Settings.Global.putString(mMockContentResolver,
+                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, null);
+        mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
+                "TestProperty");
+        propValue = mTestMapper.systemPropertiesGet("TestProperty");
+        Assert.assertEquals("", propValue);
+    }
+
+    @Test
+    public void testUpdatePropertiesFromGlobalSettings_PropertyAndSettingNotPresent() {
+        // Test that empty property will not not be set if setting is not set
+        mTestMapper.updatePropertiesFromGlobalSettings();
+        String propValue = mTestMapper.systemPropertiesGet("TestProperty");
+        Assert.assertNull("Property should not be set if setting is null", propValue);
+    }
+
+    private static class TestMapper extends GlobalSettingsToPropertiesMapper {
+        private final Map<String, String> mProps = new HashMap<>();
+
+        TestMapper(ContentResolver contentResolver) {
+            super(contentResolver, TEST_MAPPING);
+        }
+
+        @Override
+        protected String systemPropertiesGet(String key) {
+            Preconditions.checkNotNull(key);
+            return mProps.get(key);
+        }
+
+        @Override
+        protected void systemPropertiesSet(String key, String value) {
+            Preconditions.checkNotNull(value);
+            Preconditions.checkNotNull(key);
+            mProps.put(key, value);
+        }
+    }
+
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchParamsControllerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchParamsControllerTests.java
new file mode 100644
index 0000000..161c287
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/LaunchParamsControllerTests.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2017 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 android.app.ActivityOptions;
+import android.content.pm.ActivityInfo.WindowLayout;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.am.LaunchParamsController.LaunchParams;
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.android.server.am.LaunchParamsController.LaunchParamsModifier;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
+import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
+import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+/**
+ * Tests for exercising {@link LaunchParamsController}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:LaunchParamsControllerTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LaunchParamsControllerTests extends ActivityTestsBase {
+    private ActivityManagerService mService;
+    private LaunchParamsController mController;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mService = createActivityManagerService();
+        mController = new LaunchParamsController(mService);
+    }
+
+    /**
+     * Makes sure positioners get values passed to controller.
+     */
+    @Test
+    public void testArgumentPropagation() {
+        final LaunchParamsModifier
+                positioner = mock(LaunchParamsModifier.class);
+        mController.registerModifier(positioner);
+
+        final ActivityRecord record = new ActivityBuilder(mService).build();
+        final ActivityRecord source = new ActivityBuilder(mService).build();
+        final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0);
+        final ActivityOptions options = mock(ActivityOptions.class);
+
+        mController.calculate(record.getTask(), layout, record, source, options,
+                new LaunchParams());
+        verify(positioner, times(1)).onCalculate(eq(record.getTask()), eq(layout), eq(record),
+                eq(source), eq(options), any(), any());
+    }
+
+    /**
+     * Ensures positioners further down the chain are not called when RESULT_DONE is returned.
+     */
+    @Test
+    public void testEarlyExit() {
+        final LaunchParamsModifier
+                ignoredPositioner = mock(LaunchParamsModifier.class);
+        final LaunchParamsModifier earlyExitPositioner =
+                (task, layout, activity, source, options, currentParams, outParams) -> RESULT_DONE;
+
+        mController.registerModifier(ignoredPositioner);
+        mController.registerModifier(earlyExitPositioner);
+
+        mController.calculate(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*source*/, null /*options*/, new LaunchParams());
+        verify(ignoredPositioner, never()).onCalculate(any(), any(), any(), any(), any(),
+                any(), any());
+    }
+
+    /**
+     * Ensures that positioners are called in the correct order.
+     */
+    @Test
+    public void testRegistration() {
+        LaunchParamsModifier earlyExitPositioner =
+                new InstrumentedPositioner(RESULT_DONE, new LaunchParams());
+
+        final LaunchParamsModifier firstPositioner = spy(earlyExitPositioner);
+
+        mController.registerModifier(firstPositioner);
+
+        mController.calculate(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*source*/, null /*options*/, new LaunchParams());
+        verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(),
+                any());
+
+        final LaunchParamsModifier secondPositioner = spy(earlyExitPositioner);
+
+        mController.registerModifier(secondPositioner);
+
+        mController.calculate(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*source*/, null /*options*/, new LaunchParams());
+        verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(),
+                any());
+        verify(secondPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(),
+                any());
+    }
+
+    /**
+     * Makes sure positioners further down the registration chain are called.
+     */
+    @Test
+    public void testPassThrough() {
+        final LaunchParamsModifier
+                positioner1 = mock(LaunchParamsModifier.class);
+        final LaunchParams params = new LaunchParams();
+        params.mWindowingMode = WINDOWING_MODE_FREEFORM;
+        params.mBounds.set(0, 0, 30, 20);
+        params.mPreferredDisplayId = 3;
+
+        final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE,
+                params);
+
+        mController.registerModifier(positioner1);
+        mController.registerModifier(positioner2);
+
+        mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
+                null /*options*/, new LaunchParams());
+
+        verify(positioner1, times(1)).onCalculate(any(), any(), any(), any(), any(),
+                eq(positioner2.getLaunchParams()), any());
+    }
+
+    /**
+     * Ensures skipped results are not propagated.
+     */
+    @Test
+    public void testSkip() {
+        final LaunchParams params1 = new LaunchParams();
+        params1.mBounds.set(0, 0, 10, 10);
+        final InstrumentedPositioner positioner1 = new InstrumentedPositioner(RESULT_SKIP, params1);
+
+        final LaunchParams params2 = new LaunchParams();
+        params2.mBounds.set(0, 0, 20, 30);
+        final InstrumentedPositioner positioner2 =
+                new InstrumentedPositioner(RESULT_CONTINUE, params2);
+
+        mController.registerModifier(positioner1);
+        mController.registerModifier(positioner2);
+
+        final LaunchParams
+                result = new LaunchParams();
+
+        mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
+                null /*options*/, result);
+
+        assertEquals(result, positioner2.getLaunchParams());
+    }
+
+    /**
+     * Ensures that {@link LaunchParamsModifier} requests specifying display id during
+     * layout are honored.
+     */
+    @Test
+    public void testLayoutTaskPreferredDisplayChange() {
+        final LaunchParams params = new LaunchParams();
+        params.mPreferredDisplayId = 2;
+        final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params);
+        final TaskRecord task = new TaskBuilder(mService.mStackSupervisor).build();
+
+        mController.registerModifier(positioner);
+
+        doNothing().when(mService).moveStackToDisplay(anyInt(), anyInt());
+        mController.layoutTask(task, null /* windowLayout */);
+        verify(mService, times(1)).moveStackToDisplay(eq(task.getStackId()),
+                eq(params.mPreferredDisplayId));
+    }
+
+    /**
+     * Ensures that {@link LaunchParamsModifier} requests specifying windowingMode during
+     * layout are honored.
+     */
+    @Test
+    public void testLayoutTaskWindowingModeChange() {
+        final LaunchParams params = new LaunchParams();
+        final int windowingMode = WINDOWING_MODE_FREEFORM;
+        params.mWindowingMode = windowingMode;
+        final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params);
+        final TaskRecord task = new TaskBuilder(mService.mStackSupervisor).build();
+
+        mController.registerModifier(positioner);
+
+        final int beforeWindowMode = task.getStack().getWindowingMode();
+        assertNotEquals(beforeWindowMode, windowingMode);
+
+        mController.layoutTask(task, null /* windowLayout */);
+
+        final int afterWindowMode = task.getStack().getWindowingMode();
+        assertEquals(afterWindowMode, windowingMode);
+    }
+
+    public static class InstrumentedPositioner implements
+            LaunchParamsModifier {
+
+        final private int mReturnVal;
+        final private LaunchParams mParams;
+
+        InstrumentedPositioner(int returnVal, LaunchParams params) {
+            mReturnVal = returnVal;
+            mParams = params;
+        }
+
+        @Override
+        public int onCalculate(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+                   ActivityRecord source, ActivityOptions options,
+                   LaunchParams currentParams, LaunchParams outParams) {
+            outParams.set(mParams);
+            return mReturnVal;
+        }
+
+        LaunchParams getLaunchParams() {
+            return mParams;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
new file mode 100644
index 0000000..f46d712
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
@@ -0,0 +1,680 @@
+/*
+ * Copyright 2017, 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.ActivityManager.LOCK_TASK_MODE_LOCKED;
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
+import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
+import static android.app.StatusBarManager.DISABLE2_MASK;
+import static android.app.StatusBarManager.DISABLE2_NONE;
+import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
+import static android.app.StatusBarManager.DISABLE_HOME;
+import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
+import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.os.Process.SYSTEM_UID;
+import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
+
+import static com.android.server.am.LockTaskController.STATUS_BAR_MASK_LOCKED;
+import static com.android.server.am.LockTaskController.STATUS_BAR_MASK_PINNED;
+
+import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import android.app.StatusBarManager;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.IDevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.telecom.TelecomManager;
+import android.testing.DexmakerShareClassLoaderRule;
+import android.util.Pair;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.WindowManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.verification.VerificationMode;
+
+/**
+ * Unit tests for {@link LockTaskController}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.am.LockTaskControllerTest
+ */
+@Presubmit
+@SmallTest
+public class LockTaskControllerTest {
+    private static final String TEST_PACKAGE_NAME = "com.test.package";
+    private static final String TEST_PACKAGE_NAME_2 = "com.test.package2";
+    private static final String TEST_CLASS_NAME = ".TestClass";
+    private static final int TEST_USER_ID = 123;
+    private static final int TEST_UID = 10467;
+
+    @Rule
+    public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+            new DexmakerShareClassLoaderRule();
+
+    @Mock private ActivityStackSupervisor mSupervisor;
+    @Mock private IDevicePolicyManager mDevicePolicyManager;
+    @Mock private IStatusBarService mStatusBarService;
+    @Mock private WindowManagerService mWindowManager;
+    @Mock private LockPatternUtils mLockPatternUtils;
+    @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+    @Mock private TelecomManager mTelecomManager;
+    @Mock private RecentTasks mRecentTasks;
+
+    private LockTaskController mLockTaskController;
+    private Context mContext;
+    private String mLockToAppSetting;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = InstrumentationRegistry.getTargetContext();
+        mLockToAppSetting = Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.LOCK_TO_APP_EXIT_LOCKED);
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        mSupervisor.mRecentTasks = mRecentTasks;
+
+        mLockTaskController = new LockTaskController(mContext, mSupervisor,
+                new ImmediatelyExecuteHandler());
+        mLockTaskController.setWindowManager(mWindowManager);
+        mLockTaskController.mStatusBarService = mStatusBarService;
+        mLockTaskController.mDevicePolicyManager = mDevicePolicyManager;
+        mLockTaskController.mTelecomManager = mTelecomManager;
+        mLockTaskController.mLockPatternUtils = mLockPatternUtils;
+
+        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, mLockToAppSetting);
+    }
+
+    @Test
+    public void testPreconditions() {
+        // GIVEN nothing has happened
+
+        // THEN current lock task mode should be NONE
+        assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
+    }
+
+    @Test
+    public void testStartLockTaskMode_once() throws Exception {
+        // GIVEN a task record with whitelisted auth
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+
+        // WHEN calling setLockTaskMode for LOCKED mode without resuming
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // THEN the lock task mode state should be LOCKED
+        assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
+        // THEN the task should be locked
+        assertTrue(mLockTaskController.isTaskLocked(tr));
+
+        // THEN lock task mode should be started
+        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
+    }
+
+    @Test
+    public void testStartLockTaskMode_twice() throws Exception {
+        // GIVEN two task records with whitelisted auth
+        TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+
+        // WHEN calling setLockTaskMode for LOCKED mode on both tasks
+        mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
+        mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
+
+        // THEN the lock task mode state should be LOCKED
+        assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
+        // THEN neither of the tasks should be able to move to back of stack
+        assertTrue(mLockTaskController.isTaskLocked(tr1));
+        assertTrue(mLockTaskController.isTaskLocked(tr2));
+
+        // THEN lock task mode should be started
+        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
+    }
+
+    @Test
+    public void testStartLockTaskMode_pinningRequest() throws Exception {
+        // GIVEN a task record that is not whitelisted, i.e. with pinned auth
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE);
+
+        // WHEN calling startLockTaskMode
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // THEN a pinning request should be shown
+        verify(mStatusBarManagerInternal).showScreenPinningRequest(anyInt());
+    }
+
+    @Test
+    public void testStartLockTaskMode_pinnedBySystem() throws Exception {
+        // GIVEN a task record with pinned auth
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE);
+
+        // WHEN the system calls startLockTaskMode
+        mLockTaskController.startLockTaskMode(tr, true, SYSTEM_UID);
+
+        // THEN the lock task mode state should be PINNED
+        assertEquals(LOCK_TASK_MODE_PINNED, mLockTaskController.getLockTaskModeState());
+        // THEN the task should be locked
+        assertTrue(mLockTaskController.isTaskLocked(tr));
+
+        // THEN lock task mode should be started
+        verifyLockTaskStarted(STATUS_BAR_MASK_PINNED, DISABLE2_NONE);
+        // THEN screen pinning toast should be shown
+        verify(mStatusBarService).showPinningEnterExitToast(true /* entering */);
+    }
+
+    @Test
+    public void testLockTaskViolation() throws Exception {
+        // GIVEN one task record with whitelisted auth that is in lock task mode
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // THEN it's not a lock task violation to try and launch this task without clearing
+        assertFalse(mLockTaskController.isLockTaskModeViolation(tr, false));
+
+        // THEN it's a lock task violation to launch another task that is not whitelisted
+        assertTrue(mLockTaskController.isLockTaskModeViolation(getTaskRecord(
+                TaskRecord.LOCK_TASK_AUTH_PINNABLE)));
+        // THEN it's a lock task violation to launch another task that is disallowed from lock task
+        assertTrue(mLockTaskController.isLockTaskModeViolation(getTaskRecord(
+                TaskRecord.LOCK_TASK_AUTH_DONT_LOCK)));
+
+        // THEN it's no a lock task violation to launch another task that is whitelisted
+        assertFalse(mLockTaskController.isLockTaskModeViolation(getTaskRecord(
+                TaskRecord.LOCK_TASK_AUTH_WHITELISTED)));
+        assertFalse(mLockTaskController.isLockTaskModeViolation(getTaskRecord(
+                TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE)));
+        // THEN it's not a lock task violation to launch another task that is priv launchable
+        assertFalse(mLockTaskController.isLockTaskModeViolation(getTaskRecord(
+                TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV)));
+    }
+
+    @Test
+    public void testLockTaskViolation_emergencyCall() throws Exception {
+        // GIVEN one task record with whitelisted auth that is in lock task mode
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // GIVEN tasks necessary for emergency calling
+        TaskRecord keypad = getTaskRecord(new Intent().setComponent(EMERGENCY_DIALER_COMPONENT),
+                TaskRecord.LOCK_TASK_AUTH_PINNABLE);
+        TaskRecord callAction = getTaskRecord(new Intent(Intent.ACTION_CALL_EMERGENCY),
+                TaskRecord.LOCK_TASK_AUTH_PINNABLE);
+        TaskRecord dialer = getTaskRecord("com.example.dialer", TaskRecord.LOCK_TASK_AUTH_PINNABLE);
+        when(mTelecomManager.getSystemDialerPackage())
+                .thenReturn(dialer.intent.getComponent().getPackageName());
+
+        // GIVEN keyguard is allowed for lock task mode
+        mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_KEYGUARD);
+
+        // THEN the above tasks should all be allowed
+        assertFalse(mLockTaskController.isLockTaskModeViolation(keypad));
+        assertFalse(mLockTaskController.isLockTaskModeViolation(callAction));
+        assertFalse(mLockTaskController.isLockTaskModeViolation(dialer));
+
+        // GIVEN keyguard is disallowed for lock task mode (default)
+        mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NONE);
+
+        // THEN the above tasks should all be blocked
+        assertTrue(mLockTaskController.isLockTaskModeViolation(keypad));
+        assertTrue(mLockTaskController.isLockTaskModeViolation(callAction));
+        assertTrue(mLockTaskController.isLockTaskModeViolation(dialer));
+    }
+
+    @Test
+    public void testStopLockTaskMode() throws Exception {
+        // GIVEN one task record with whitelisted auth that is in lock task mode
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // WHEN the same caller calls stopLockTaskMode
+        mLockTaskController.stopLockTaskMode(tr, false, TEST_UID);
+
+        // THEN the lock task mode should be NONE
+        assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
+        // THEN the task should no longer be locked
+        assertFalse(mLockTaskController.isTaskLocked(tr));
+        // THEN lock task mode should have been finished
+        verifyLockTaskStopped(times(1));
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testStopLockTaskMode_differentCaller() throws Exception {
+        // GIVEN one task record with whitelisted auth that is in lock task mode
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // WHEN a different caller calls stopLockTaskMode
+        mLockTaskController.stopLockTaskMode(tr, false, TEST_UID + 1);
+
+        // THEN security exception should be thrown, because different caller tried to unlock
+    }
+
+    @Test
+    public void testStopLockTaskMode_systemCaller() throws Exception {
+        // GIVEN one task record with whitelisted auth that is in lock task mode
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // WHEN system calls stopLockTaskMode
+        mLockTaskController.stopLockTaskMode(tr, true, SYSTEM_UID);
+
+        // THEN lock task mode should still be active
+        assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
+    }
+
+    @Test
+    public void testStopLockTaskMode_twoTasks() throws Exception {
+        // GIVEN two task records with whitelisted auth that is in lock task mode
+        TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
+        mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
+
+        // WHEN calling stopLockTaskMode
+        mLockTaskController.stopLockTaskMode(tr2, false, TEST_UID);
+
+        // THEN the lock task mode should still be active
+        assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
+        // THEN the first task should still be locked
+        assertTrue(mLockTaskController.isTaskLocked(tr1));
+        // THEN the top task should no longer be locked
+        assertFalse(mLockTaskController.isTaskLocked(tr2));
+        // THEN lock task mode should not have been finished
+        verifyLockTaskStopped(never());
+    }
+
+    @Test
+    public void testStopLockTaskMode_rootTask() throws Exception {
+        // GIVEN two task records with whitelisted auth that is in lock task mode
+        TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
+        mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
+
+        // WHEN calling stopLockTaskMode on the root task
+        mLockTaskController.stopLockTaskMode(tr1, false, TEST_UID);
+
+        // THEN the lock task mode should be inactive
+        assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
+        // THEN the first task should no longer be locked
+        assertFalse(mLockTaskController.isTaskLocked(tr1));
+        // THEN the top task should no longer be locked
+        assertFalse(mLockTaskController.isTaskLocked(tr2));
+        // THEN lock task mode should be finished
+        verifyLockTaskStopped(times(1));
+    }
+
+    @Test
+    public void testStopLockTaskMode_pinned() throws Exception {
+        // GIVEN one task records that is in pinned mode
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE);
+        mLockTaskController.startLockTaskMode(tr, true, SYSTEM_UID);
+        // GIVEN that the keyguard is required to show after unlocking
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, 1);
+
+        // reset invocation counter
+        reset(mStatusBarService);
+
+        // WHEN calling stopLockTask
+        mLockTaskController.stopLockTaskMode(null, true, SYSTEM_UID);
+
+        // THEN the lock task mode should no longer be active
+        assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
+        // THEN the task should no longer be locked
+        assertFalse(mLockTaskController.isTaskLocked(tr));
+        // THEN lock task mode should have been finished
+        verifyLockTaskStopped(times(1));
+        // THEN the keyguard should be shown
+        verify(mLockPatternUtils).requireCredentialEntry(UserHandle.USER_ALL);
+        // THEN screen pinning toast should be shown
+        verify(mStatusBarService).showPinningEnterExitToast(false /* entering */);
+    }
+
+    @Test
+    public void testClearLockedTasks() throws Exception {
+        // GIVEN two task records with whitelisted auth that is in lock task mode
+        TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
+        mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
+
+        // WHEN calling stopLockTaskMode on the root task
+        mLockTaskController.clearLockedTasks("testClearLockedTasks");
+
+        // THEN the lock task mode should be inactive
+        assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
+        // THEN the first task should no longer be locked
+        assertFalse(mLockTaskController.isTaskLocked(tr1));
+        // THEN the top task should no longer be locked
+        assertFalse(mLockTaskController.isTaskLocked(tr2));
+        // THEN lock task mode should be finished
+        verifyLockTaskStopped(times(1));
+    }
+
+    @Test
+    public void testUpdateLockTaskPackages() throws Exception {
+        String[] whitelist1 = {TEST_PACKAGE_NAME, TEST_PACKAGE_NAME_2};
+        String[] whitelist2 = {TEST_PACKAGE_NAME};
+
+        // No package is whitelisted initially
+        for (String pkg : whitelist1) {
+            assertFalse("Package shouldn't be whitelisted: " + pkg,
+                    mLockTaskController.isPackageWhitelisted(TEST_USER_ID, pkg));
+            assertFalse("Package shouldn't be whitelisted for user 0: " + pkg,
+                    mLockTaskController.isPackageWhitelisted(0, pkg));
+        }
+
+        // Apply whitelist
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist1);
+
+        // Assert the whitelist is applied to the correct user
+        for (String pkg : whitelist1) {
+            assertTrue("Package should be whitelisted: " + pkg,
+                    mLockTaskController.isPackageWhitelisted(TEST_USER_ID, pkg));
+            assertFalse("Package shouldn't be whitelisted for user 0: " + pkg,
+                    mLockTaskController.isPackageWhitelisted(0, pkg));
+        }
+
+        // Update whitelist
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist2);
+
+        // Assert the new whitelist is applied
+        assertTrue("Package should remain whitelisted: " + TEST_PACKAGE_NAME,
+                mLockTaskController.isPackageWhitelisted(TEST_USER_ID, TEST_PACKAGE_NAME));
+        assertFalse("Package should no longer be whitelisted: " + TEST_PACKAGE_NAME_2,
+                mLockTaskController.isPackageWhitelisted(TEST_USER_ID, TEST_PACKAGE_NAME_2));
+    }
+
+    @Test
+    public void testUpdateLockTaskPackages_taskRemoved() throws Exception {
+        // GIVEN two tasks which are whitelisted initially
+        TaskRecord tr1 = getTaskRecordForUpdate(TEST_PACKAGE_NAME, true);
+        TaskRecord tr2 = getTaskRecordForUpdate(TEST_PACKAGE_NAME_2, false);
+        String[] whitelist = {TEST_PACKAGE_NAME, TEST_PACKAGE_NAME_2};
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+
+        // GIVEN the tasks are launched into LockTask mode
+        mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
+        mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
+        assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
+        assertTrue(mLockTaskController.isTaskLocked(tr1));
+        assertTrue(mLockTaskController.isTaskLocked(tr2));
+        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
+
+        // WHEN removing one package from whitelist
+        whitelist = new String[] {TEST_PACKAGE_NAME};
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+
+        // THEN the task running that package should be stopped
+        verify(tr2).performClearTaskLocked();
+        assertFalse(mLockTaskController.isTaskLocked(tr2));
+        // THEN the other task should remain locked
+        assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
+        assertTrue(mLockTaskController.isTaskLocked(tr1));
+        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
+
+        // WHEN removing the last package from whitelist
+        whitelist = new String[] {};
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+
+        // THEN the last task should be cleared, and the system should quit LockTask mode
+        verify(tr1).performClearTaskLocked();
+        assertFalse(mLockTaskController.isTaskLocked(tr1));
+        assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
+        verifyLockTaskStopped(times(1));
+    }
+
+    @Test
+    public void testUpdateLockTaskFeatures() throws Exception {
+        // GIVEN a locked task
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // THEN lock task mode should be started with default status bar masks
+        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
+
+        // reset invocation counter
+        reset(mStatusBarService);
+
+        // WHEN home button is enabled for lock task mode
+        mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_HOME);
+
+        // THEN status bar should be updated to reflect this change
+        int expectedFlags = STATUS_BAR_MASK_LOCKED
+                & ~DISABLE_HOME;
+        int expectedFlags2 = DISABLE2_MASK;
+        verify(mStatusBarService).disable(eq(expectedFlags), any(IBinder.class),
+                eq(mContext.getPackageName()));
+        verify(mStatusBarService).disable2(eq(expectedFlags2), any(IBinder.class),
+                eq(mContext.getPackageName()));
+
+        // reset invocation counter
+        reset(mStatusBarService);
+
+        // WHEN notifications are enabled for lock task mode
+        mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NOTIFICATIONS);
+
+        // THEN status bar should be updated to reflect this change
+        expectedFlags = STATUS_BAR_MASK_LOCKED
+                & ~DISABLE_NOTIFICATION_ICONS
+                & ~DISABLE_NOTIFICATION_ALERTS;
+        expectedFlags2 = DISABLE2_MASK
+                & ~DISABLE2_NOTIFICATION_SHADE;
+        verify(mStatusBarService).disable(eq(expectedFlags), any(IBinder.class),
+                eq(mContext.getPackageName()));
+        verify(mStatusBarService).disable2(eq(expectedFlags2), any(IBinder.class),
+                eq(mContext.getPackageName()));
+    }
+
+    @Test
+    public void testUpdateLockTaskFeatures_differentUser() throws Exception {
+        // GIVEN a locked task
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // THEN lock task mode should be started with default status bar masks
+        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
+
+        // reset invocation counter
+        reset(mStatusBarService);
+
+        // WHEN home button is enabled for lock task mode for another user
+        mLockTaskController.updateLockTaskFeatures(TEST_USER_ID + 1, LOCK_TASK_FEATURE_HOME);
+
+        // THEN status bar shouldn't change
+        verify(mStatusBarService, never()).disable(anyInt(), any(IBinder.class),
+                eq(mContext.getPackageName()));
+        verify(mStatusBarService, never()).disable2(anyInt(), any(IBinder.class),
+                eq(mContext.getPackageName()));
+    }
+
+    @Test
+    public void testUpdateLockTaskFeatures_keyguard() throws Exception {
+        // GIVEN a locked task
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // THEN keyguard should be disabled
+        verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString());
+
+        // WHEN keyguard is enabled for lock task mode
+        mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_KEYGUARD);
+
+        // THEN keyguard should be enabled
+        verify(mWindowManager).reenableKeyguard(any(IBinder.class));
+
+        // WHEN keyguard is disabled again for lock task mode
+        mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NONE);
+
+        // THEN keyguard should be disabled
+        verify(mWindowManager, times(2)).disableKeyguard(any(IBinder.class), anyString());
+    }
+
+    @Test
+    public void testGetStatusBarDisableFlags() {
+        // Note that we don't enumerate all StatusBarManager flags, but only choose a subset to test
+
+        // WHEN nothing is enabled
+        Pair<Integer, Integer> flags = mLockTaskController.getStatusBarDisableFlags(
+                LOCK_TASK_FEATURE_NONE);
+        // THEN unsupported feature flags should still be untouched
+        assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0);
+        // THEN everything else should be disabled
+        assertTrue((StatusBarManager.DISABLE_CLOCK & flags.first) != 0);
+        assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0);
+
+        // WHEN only home button is enabled
+        flags = mLockTaskController.getStatusBarDisableFlags(
+                LOCK_TASK_FEATURE_HOME);
+        // THEN unsupported feature flags should still be untouched
+        assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0);
+        // THEN home button should indeed be enabled
+        assertTrue((StatusBarManager.DISABLE_HOME & flags.first) == 0);
+        // THEN other feature flags should remain disabled
+        assertTrue((StatusBarManager.DISABLE2_NOTIFICATION_SHADE & flags.second) != 0);
+
+        // WHEN only global actions menu and notifications are enabled
+        flags = mLockTaskController.getStatusBarDisableFlags(
+                DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+                        | DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS);
+        // THEN unsupported feature flags should still be untouched
+        assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0);
+        // THEN notifications should be enabled
+        assertTrue((StatusBarManager.DISABLE_NOTIFICATION_ICONS & flags.first) == 0);
+        assertTrue((StatusBarManager.DISABLE_NOTIFICATION_ALERTS & flags.first) == 0);
+        assertTrue((StatusBarManager.DISABLE2_NOTIFICATION_SHADE & flags.second) == 0);
+        // THEN global actions should be enabled
+        assertTrue((StatusBarManager.DISABLE2_GLOBAL_ACTIONS & flags.second) == 0);
+        // THEN quick settings should still be disabled
+        assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0);
+    }
+
+    private TaskRecord getTaskRecord(int lockTaskAuth) {
+        return getTaskRecord(TEST_PACKAGE_NAME, lockTaskAuth);
+    }
+
+    private TaskRecord getTaskRecord(String pkg, int lockTaskAuth) {
+        final Intent intent = new Intent()
+                .setComponent(ComponentName.createRelative(pkg, TEST_CLASS_NAME));
+        return getTaskRecord(intent, lockTaskAuth);
+    }
+
+    private TaskRecord getTaskRecord(Intent intent, int lockTaskAuth) {
+        TaskRecord tr = mock(TaskRecord.class);
+        tr.mLockTaskAuth = lockTaskAuth;
+        tr.intent = intent;
+        tr.userId = TEST_USER_ID;
+        return tr;
+    }
+
+    /**
+     * @param isAppAware {@code true} if the app has marked if_whitelisted in its manifest
+     */
+    private TaskRecord getTaskRecordForUpdate(String pkg, boolean isAppAware) {
+        final int authIfWhitelisted = isAppAware
+                ? TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE
+                : TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
+        TaskRecord tr = getTaskRecord(pkg, authIfWhitelisted);
+        doAnswer((invocation) -> {
+            boolean isWhitelisted =
+                    mLockTaskController.isPackageWhitelisted(TEST_USER_ID, pkg);
+            tr.mLockTaskAuth = isWhitelisted
+                    ? authIfWhitelisted
+                    : TaskRecord.LOCK_TASK_AUTH_PINNABLE;
+            return null;
+        }).when(tr).setLockTaskAuth();
+        return tr;
+    }
+
+    private void verifyLockTaskStarted(int statusBarMask, int statusBarMask2) throws Exception {
+        // THEN the keyguard should have been disabled
+        verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString());
+        // THEN the status bar should have been disabled
+        verify(mStatusBarService).disable(eq(statusBarMask), any(IBinder.class),
+                eq(mContext.getPackageName()));
+        verify(mStatusBarService).disable2(eq(statusBarMask2), any(IBinder.class),
+                eq(mContext.getPackageName()));
+        // THEN recents should have been notified
+        verify(mRecentTasks).onLockTaskModeStateChanged(anyInt(), eq(TEST_USER_ID));
+        // THEN the DO/PO should be informed about the operation
+        verify(mDevicePolicyManager).notifyLockTaskModeChanged(true, TEST_PACKAGE_NAME,
+                TEST_USER_ID);
+    }
+
+    private void verifyLockTaskStopped(VerificationMode mode) throws Exception {
+        // THEN the keyguard should have been disabled
+        verify(mWindowManager, mode).reenableKeyguard(any(IBinder.class));
+        // THEN the status bar should have been disabled
+        verify(mStatusBarService, mode).disable(eq(StatusBarManager.DISABLE_NONE),
+                any(IBinder.class), eq(mContext.getPackageName()));
+        verify(mStatusBarService, mode).disable2(eq(StatusBarManager.DISABLE2_NONE),
+                any(IBinder.class), eq(mContext.getPackageName()));
+        // THEN the DO/PO should be informed about the operation
+        verify(mDevicePolicyManager, mode).notifyLockTaskModeChanged(false, null, TEST_USER_ID);
+    }
+
+    /**
+     * Special handler implementation that executes any message / runnable posted immediately on the
+     * thread that it's posted on rather than enqueuing them on its looper.
+     */
+    private static class ImmediatelyExecuteHandler extends Handler {
+        @Override
+        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+            if (msg.getCallback() != null) {
+                msg.getCallback().run();
+            }
+            return true;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
new file mode 100644
index 0000000..5518ca5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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 com.android.server.am.MemoryStatUtil.parseMemoryStatFromMemcg;
+import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromProcfs;
+import static com.android.server.am.MemoryStatUtil.MemoryStat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MemoryStatUtilTest {
+    private String MEMORY_STAT_CONTENTS = String.join(
+            "\n",
+            "cache 96", // keep different from total_cache to catch reading wrong value
+            "rss 97", // keep different from total_rss to catch reading wrong value
+            "rss_huge 0",
+            "mapped_file 524288",
+            "writeback 0",
+            "swap 95", // keep different from total_rss to catch reading wrong value
+            "pgpgin 16717",
+            "pgpgout 5037",
+            "pgfault 99", // keep different from total_pgfault to catch reading wrong value
+            "pgmajfault 98", // keep different from total_pgmajfault to catch reading wrong value
+            "inactive_anon 503808",
+            "active_anon 46309376",
+            "inactive_file 876544",
+            "active_file 81920",
+            "unevictable 0",
+            "hierarchical_memory_limit 18446744073709551615",
+            "hierarchical_memsw_limit 18446744073709551615",
+            "total_cache 4",
+            "total_rss 3",
+            "total_rss_huge 0",
+            "total_mapped_file 524288",
+            "total_writeback 0",
+            "total_swap 5",
+            "total_pgpgin 16717",
+            "total_pgpgout 5037",
+            "total_pgfault 1",
+            "total_pgmajfault 2",
+            "total_inactive_anon 503808",
+            "total_active_anon 46309376",
+            "total_inactive_file 876544",
+            "total_active_file 81920",
+            "total_unevictable 0");
+
+    private String PROC_STAT_CONTENTS = String.join(
+            " ",
+            "1040",
+            "(system_server)",
+            "S",
+            "544",
+            "544",
+            "0",
+            "0",
+            "-1",
+            "1077936448",
+            "1", // this is pgfault
+            "0",
+            "2", // this is pgmajfault
+            "0",
+            "44533",
+            "13471",
+            "0",
+            "0",
+            "18",
+            "-2",
+            "117",
+            "0",
+            "2206",
+            "1257177088",
+            "3", // this is rss in bytes
+            "4294967295",
+            "2936971264",
+            "2936991289",
+            "3198888320",
+            "3198879848",
+            "2903927664",
+            "0",
+            "4612",
+            "0",
+            "1073775864",
+            "4294967295",
+            "0",
+            "0",
+            "17",
+            "0",
+            "0",
+            "0",
+            "0",
+            "0",
+            "0",
+            "2936999088",
+            "2936999936",
+            "2958692352",
+            "3198888595",
+            "3198888671",
+            "3198888671",
+            "3198889956",
+            "0");
+
+    @Test
+    public void testParseMemoryStatFromMemcg_parsesCorrectValues() throws Exception {
+        MemoryStat stat = parseMemoryStatFromMemcg(MEMORY_STAT_CONTENTS);
+        assertEquals(stat.pgfault, 1);
+        assertEquals(stat.pgmajfault, 2);
+        assertEquals(stat.rssInBytes, 3);
+        assertEquals(stat.cacheInBytes, 4);
+        assertEquals(stat.swapInBytes, 5);
+    }
+
+    @Test
+    public void testParseMemoryStatFromMemcg_emptyMemoryStatContents() throws Exception {
+        MemoryStat stat = parseMemoryStatFromMemcg("");
+        assertNull(stat);
+
+        stat = parseMemoryStatFromMemcg(null);
+        assertNull(stat);
+    }
+
+    @Test
+    public void testParseMemoryStatFromProcfs_parsesCorrectValues() throws Exception {
+        MemoryStat stat = parseMemoryStatFromProcfs(PROC_STAT_CONTENTS);
+        assertEquals(1, stat.pgfault);
+        assertEquals(2, stat.pgmajfault);
+        assertEquals(3, stat.rssInBytes);
+        assertEquals(0, stat.cacheInBytes);
+        assertEquals(0, stat.swapInBytes);
+    }
+
+    @Test
+    public void testParseMemoryStatFromProcfs_emptyContents() throws Exception {
+        MemoryStat stat = parseMemoryStatFromProcfs("");
+        assertNull(stat);
+
+        stat = parseMemoryStatFromProcfs(null);
+        assertNull(stat);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java b/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java
new file mode 100644
index 0000000..2baf995
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+import android.view.RemoteAnimationAdapter;
+
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * atest PendingRemoteAnimationRegistryTest
+ */
+@SmallTest
+@Presubmit
+@FlakyTest
+@RunWith(AndroidJUnit4.class)
+public class PendingRemoteAnimationRegistryTest extends ActivityTestsBase {
+
+    @Mock RemoteAnimationAdapter mAdapter;
+    private PendingRemoteAnimationRegistry mRegistry;
+    private final OffsettableClock mClock = new OffsettableClock.Stopped();
+    private TestHandler mHandler;
+    private ActivityManagerService mService;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        mService = createActivityManagerService();
+        mService.mHandlerThread.getThreadHandler().runWithScissors(() -> {
+            mHandler = new TestHandler(null, mClock);
+        }, 0);
+        mRegistry = new PendingRemoteAnimationRegistry(mService, mHandler);
+    }
+
+    @Test
+    public void testOverrideActivityOptions() {
+        mRegistry.addPendingAnimation("com.android.test", mAdapter);
+        ActivityOptions opts = ActivityOptions.makeBasic();
+        opts = mRegistry.overrideOptionsIfNeeded("com.android.test", opts);
+        assertEquals(mAdapter, opts.getRemoteAnimationAdapter());
+    }
+
+    @Test
+    public void testOverrideActivityOptions_null() {
+        mRegistry.addPendingAnimation("com.android.test", mAdapter);
+        final ActivityOptions opts = mRegistry.overrideOptionsIfNeeded("com.android.test", null);
+        assertNotNull(opts);
+        assertEquals(mAdapter, opts.getRemoteAnimationAdapter());
+    }
+
+    @Test
+    public void testTimeout() {
+        mRegistry.addPendingAnimation("com.android.test", mAdapter);
+        mClock.fastForward(5000);
+        mHandler.timeAdvance();
+        assertNull(mRegistry.overrideOptionsIfNeeded("com.android.test", null));
+    }
+
+    @Test
+    public void testTimeout_overridenEntry() {
+        mRegistry.addPendingAnimation("com.android.test", mAdapter);
+        mClock.fastForward(2500);
+        mHandler.timeAdvance();
+        mRegistry.addPendingAnimation("com.android.test", mAdapter);
+        mClock.fastForward(1000);
+        mHandler.timeAdvance();
+        final ActivityOptions opts = mRegistry.overrideOptionsIfNeeded("com.android.test", null);
+        assertEquals(mAdapter, opts.getRemoteAnimationAdapter());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java b/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java
new file mode 100644
index 0000000..54f93a8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2017 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 org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.IDeviceAdminService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+
+import org.mockito.ArgumentMatchers;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+@SmallTest
+public class PersistentConnectionTest extends AndroidTestCase {
+    private static class MyConnection extends PersistentConnection<IDeviceAdminService> {
+        public long uptimeMillis = 12345;
+
+        public ArrayList<Pair<Runnable, Long>> scheduledRunnables = new ArrayList<>();
+
+        public MyConnection(String tag, Context context, Handler handler, int userId,
+                ComponentName componentName, long rebindBackoffSeconds,
+                double rebindBackoffIncrease, long rebindMaxBackoffSeconds) {
+            super(tag, context, handler, userId, componentName,
+                    rebindBackoffSeconds, rebindBackoffIncrease, rebindMaxBackoffSeconds);
+        }
+
+        @Override
+        protected IDeviceAdminService asInterface(IBinder binder) {
+            return (IDeviceAdminService) binder;
+        }
+
+        @Override
+        long injectUptimeMillis() {
+            return uptimeMillis;
+        }
+
+        @Override
+        void injectPostAtTime(Runnable r, long uptimeMillis) {
+            scheduledRunnables.add(Pair.create(r, uptimeMillis));
+        }
+
+        @Override
+        void injectRemoveCallbacks(Runnable r) {
+            for (int i = scheduledRunnables.size() - 1; i >= 0; i--) {
+                if (scheduledRunnables.get(i).first.equals(r)) {
+                    scheduledRunnables.remove(i);
+                }
+            }
+        }
+
+        void elapse(long milliSeconds) {
+            uptimeMillis += milliSeconds;
+
+            // Fire the scheduled runnables.
+
+            // Note we collect first and then run all, because sometimes a scheduled runnable
+            // calls removeCallbacks.
+            final ArrayList<Runnable> list = new ArrayList<>();
+
+            for (int i = scheduledRunnables.size() - 1; i >= 0; i--) {
+                if (scheduledRunnables.get(i).second <= uptimeMillis) {
+                    list.add(scheduledRunnables.get(i).first);
+                    scheduledRunnables.remove(i);
+                }
+            }
+
+            Collections.reverse(list);
+            for (Runnable r : list) {
+                r.run();
+            }
+        }
+    }
+
+    public void testAll() {
+        final Context context = mock(Context.class);
+        final int userId = 11;
+        final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def");
+        final Handler handler = new Handler(Looper.getMainLooper());
+
+        final MyConnection conn = new MyConnection("tag", context, handler, userId, cn,
+                /* rebindBackoffSeconds= */ 5,
+                /* rebindBackoffIncrease= */ 1.5,
+                /* rebindMaxBackoffSeconds= */ 11);
+
+        assertFalse(conn.isBound());
+        assertFalse(conn.isConnected());
+        assertFalse(conn.isRebindScheduled());
+        assertEquals(5000, conn.getNextBackoffMsForTest());
+        assertNull(conn.getServiceBinder());
+
+        when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(),
+                any(Handler.class), any(UserHandle.class)))
+                .thenReturn(true);
+
+        // Call bind.
+        conn.bind();
+
+        assertTrue(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertFalse(conn.isConnected());
+        assertFalse(conn.isRebindScheduled());
+        assertNull(conn.getServiceBinder());
+
+        assertEquals(5000, conn.getNextBackoffMsForTest());
+
+        verify(context).bindServiceAsUser(
+                ArgumentMatchers.argThat(intent -> cn.equals(intent.getComponent())),
+                eq(conn.getServiceConnectionForTest()),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(handler), eq(UserHandle.of(userId)));
+
+        // AM responds...
+        conn.getServiceConnectionForTest().onServiceConnected(cn,
+                new IDeviceAdminService.Stub() {});
+
+        assertTrue(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertTrue(conn.isConnected());
+        assertNotNull(conn.getServiceBinder());
+        assertFalse(conn.isRebindScheduled());
+
+        assertEquals(5000, conn.getNextBackoffMsForTest());
+
+
+        // Now connected.
+
+        // Call unbind...
+        conn.unbind();
+        assertFalse(conn.isBound());
+        assertFalse(conn.shouldBeBoundForTest());
+        assertFalse(conn.isConnected());
+        assertNull(conn.getServiceBinder());
+        assertFalse(conn.isRebindScheduled());
+
+        // Caller bind again...
+        conn.bind();
+
+        assertTrue(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertFalse(conn.isConnected());
+        assertFalse(conn.isRebindScheduled());
+        assertNull(conn.getServiceBinder());
+
+        assertEquals(5000, conn.getNextBackoffMsForTest());
+
+
+        // Now connected again.
+
+        // The service got killed...
+        conn.getServiceConnectionForTest().onServiceDisconnected(cn);
+
+        assertTrue(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertFalse(conn.isConnected());
+        assertNull(conn.getServiceBinder());
+        assertFalse(conn.isRebindScheduled());
+
+        assertEquals(5000, conn.getNextBackoffMsForTest());
+
+        // Connected again...
+        conn.getServiceConnectionForTest().onServiceConnected(cn,
+                new IDeviceAdminService.Stub() {});
+
+        assertTrue(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertTrue(conn.isConnected());
+        assertNotNull(conn.getServiceBinder());
+        assertFalse(conn.isRebindScheduled());
+
+        assertEquals(5000, conn.getNextBackoffMsForTest());
+
+
+        // Then the binding is "died"...
+        conn.getServiceConnectionForTest().onBindingDied(cn);
+
+        assertFalse(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertFalse(conn.isConnected());
+        assertNull(conn.getServiceBinder());
+        assertTrue(conn.isRebindScheduled());
+
+        assertEquals(7500, conn.getNextBackoffMsForTest());
+
+        assertEquals(
+                Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(),
+                        conn.uptimeMillis + 5000)),
+                conn.scheduledRunnables);
+
+        // 5000 ms later...
+        conn.elapse(5000);
+
+        assertTrue(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertFalse(conn.isConnected());
+        assertNull(conn.getServiceBinder());
+        assertFalse(conn.isRebindScheduled());
+
+        assertEquals(7500, conn.getNextBackoffMsForTest());
+
+        // Connected.
+        conn.getServiceConnectionForTest().onServiceConnected(cn,
+                new IDeviceAdminService.Stub() {});
+
+        assertTrue(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertTrue(conn.isConnected());
+        assertNotNull(conn.getServiceBinder());
+        assertFalse(conn.isRebindScheduled());
+
+        assertEquals(7500, conn.getNextBackoffMsForTest());
+
+        // Then the binding is "died"...
+        conn.getServiceConnectionForTest().onBindingDied(cn);
+
+        assertFalse(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertFalse(conn.isConnected());
+        assertNull(conn.getServiceBinder());
+        assertTrue(conn.isRebindScheduled());
+
+        assertEquals(11000, conn.getNextBackoffMsForTest());
+
+        assertEquals(
+                Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(),
+                        conn.uptimeMillis + 7500)),
+                conn.scheduledRunnables);
+
+        // Later...
+        conn.elapse(7500);
+
+        assertTrue(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertFalse(conn.isConnected());
+        assertNull(conn.getServiceBinder());
+        assertFalse(conn.isRebindScheduled());
+
+        assertEquals(11000, conn.getNextBackoffMsForTest());
+
+
+        // Then the binding is "died"...
+        conn.getServiceConnectionForTest().onBindingDied(cn);
+
+        assertFalse(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertFalse(conn.isConnected());
+        assertNull(conn.getServiceBinder());
+        assertTrue(conn.isRebindScheduled());
+
+        assertEquals(11000, conn.getNextBackoffMsForTest());
+
+        assertEquals(
+                Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(),
+                    conn.uptimeMillis + 11000)),
+                conn.scheduledRunnables);
+
+        // Call unbind...
+        conn.unbind();
+        assertFalse(conn.isBound());
+        assertFalse(conn.shouldBeBoundForTest());
+        assertFalse(conn.isConnected());
+        assertNull(conn.getServiceBinder());
+        assertFalse(conn.isRebindScheduled());
+
+        // Call bind again... And now the backoff is reset to 5000.
+        conn.bind();
+
+        assertTrue(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertFalse(conn.isConnected());
+        assertFalse(conn.isRebindScheduled());
+        assertNull(conn.getServiceBinder());
+
+        assertEquals(5000, conn.getNextBackoffMsForTest());
+    }
+
+    public void testReconnectFiresAfterUnbind() {
+        final Context context = mock(Context.class);
+        final int userId = 11;
+        final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def");
+        final Handler handler = new Handler(Looper.getMainLooper());
+
+        final MyConnection conn = new MyConnection("tag", context, handler, userId, cn,
+                /* rebindBackoffSeconds= */ 5,
+                /* rebindBackoffIncrease= */ 1.5,
+                /* rebindMaxBackoffSeconds= */ 11);
+
+        when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(),
+                any(Handler.class), any(UserHandle.class)))
+                .thenReturn(true);
+
+        // Bind.
+        conn.bind();
+
+        assertTrue(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertFalse(conn.isRebindScheduled());
+
+        conn.elapse(1000);
+
+        // Service crashes.
+        conn.getServiceConnectionForTest().onBindingDied(cn);
+
+        assertFalse(conn.isBound());
+        assertTrue(conn.shouldBeBoundForTest());
+        assertTrue(conn.isRebindScheduled());
+
+        assertEquals(7500, conn.getNextBackoffMsForTest());
+
+        // Call unbind.
+        conn.unbind();
+        assertFalse(conn.isBound());
+        assertFalse(conn.shouldBeBoundForTest());
+
+        // Now, at this point, it's possible that the scheduled runnable had already been fired
+        // before during the unbind() call, and waiting on mLock.
+        // To simulate it, we just call the runnable here.
+        conn.getBindForBackoffRunnableForTest().run();
+
+        // Should still not be bound.
+        assertFalse(conn.isBound());
+        assertFalse(conn.shouldBeBoundForTest());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
new file mode 100644
index 0000000..b73ac89
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -0,0 +1,1026 @@
+/*
+ * Copyright (C) 2017 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.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import static java.lang.Integer.MAX_VALUE;
+
+import android.annotation.TestApi;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.WindowConfiguration;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.MutableLong;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.server.am.RecentTasks.Callbacks;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * atest FrameworksServicesTests:RecentTasksTest
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RecentTasksTest extends ActivityTestsBase {
+    private static final int TEST_USER_0_ID = 0;
+    private static final int TEST_USER_1_ID = 10;
+    private static final int TEST_QUIET_USER_ID = 20;
+    private static final UserInfo DEFAULT_USER_INFO = new UserInfo();
+    private static final UserInfo QUIET_USER_INFO = new UserInfo();
+    private static int LAST_TASK_ID = 1;
+    private static int LAST_STACK_ID = 1;
+    private static int INVALID_STACK_ID = 999;
+
+    private Context mContext = InstrumentationRegistry.getContext();
+    private ActivityManagerService mService;
+    private ActivityDisplay mDisplay;
+    private ActivityDisplay mOtherDisplay;
+    private ActivityStack mStack;
+    private ActivityStack mHomeStack;
+    private TestTaskPersister mTaskPersister;
+    private TestRecentTasks mRecentTasks;
+    private TestRunningTasks mRunningTasks;
+
+    private ArrayList<TaskRecord> mTasks;
+    private ArrayList<TaskRecord> mSameDocumentTasks;
+
+    private CallbacksRecorder mCallbacksRecorder;
+
+    class TestUserController extends UserController {
+        TestUserController(ActivityManagerService service) {
+            super(service);
+        }
+
+        @Override
+        int[] getCurrentProfileIds() {
+            return new int[] { TEST_USER_0_ID, TEST_QUIET_USER_ID };
+        }
+
+        @Override
+        Set<Integer> getProfileIds(int userId) {
+            Set<Integer> profileIds = new HashSet<>();
+            profileIds.add(TEST_USER_0_ID);
+            profileIds.add(TEST_QUIET_USER_ID);
+            return profileIds;
+        }
+
+        @Override
+        UserInfo getUserInfo(int userId) {
+            switch (userId) {
+                case TEST_USER_0_ID:
+                case TEST_USER_1_ID:
+                    return DEFAULT_USER_INFO;
+                case TEST_QUIET_USER_ID:
+                    return QUIET_USER_INFO;
+            }
+            return null;
+        }
+    }
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
+        mService = setupActivityManagerService(new MyTestActivityManagerService(mContext));
+        mRecentTasks = (TestRecentTasks) mService.getRecentTasks();
+        mRecentTasks.loadParametersFromResources(mContext.getResources());
+        mHomeStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        ((MyTestActivityStackSupervisor) mService.mStackSupervisor).setHomeStack(mHomeStack);
+        mCallbacksRecorder = new CallbacksRecorder();
+        mRecentTasks.registerCallback(mCallbacksRecorder);
+        QUIET_USER_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_QUIET_MODE;
+
+        mTasks = new ArrayList<>();
+        mTasks.add(createTaskBuilder(".Task1").build());
+        mTasks.add(createTaskBuilder(".Task2").build());
+        mTasks.add(createTaskBuilder(".Task3").build());
+        mTasks.add(createTaskBuilder(".Task4").build());
+        mTasks.add(createTaskBuilder(".Task5").build());
+
+        mSameDocumentTasks = new ArrayList<>();
+        mSameDocumentTasks.add(createDocumentTask(".DocumentTask1"));
+        mSameDocumentTasks.add(createDocumentTask(".DocumentTask1"));
+    }
+
+    @Test
+    public void testCallbacks() throws Exception {
+        // Add some tasks
+        mRecentTasks.add(mTasks.get(0));
+        mRecentTasks.add(mTasks.get(1));
+        assertTrue(mCallbacksRecorder.added.contains(mTasks.get(0))
+                && mCallbacksRecorder.added.contains(mTasks.get(1)));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.isEmpty());
+        mCallbacksRecorder.clear();
+
+        // Remove some tasks
+        mRecentTasks.remove(mTasks.get(0));
+        mRecentTasks.remove(mTasks.get(1));
+        assertTrue(mCallbacksRecorder.added.isEmpty());
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.contains(mTasks.get(0)));
+        assertTrue(mCallbacksRecorder.removed.contains(mTasks.get(1)));
+        mCallbacksRecorder.clear();
+
+        // Remove the callback, ensure we don't get any calls
+        mRecentTasks.unregisterCallback(mCallbacksRecorder);
+        mRecentTasks.add(mTasks.get(0));
+        mRecentTasks.remove(mTasks.get(0));
+        assertTrue(mCallbacksRecorder.added.isEmpty());
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.isEmpty());
+    }
+
+    @Test
+    public void testAddTasksNoMultiple_expectNoTrim() throws Exception {
+        // Add same non-multiple-task document tasks will remove the task (to re-add it) but not
+        // trim it
+        TaskRecord documentTask1 = createDocumentTask(".DocumentTask1");
+        TaskRecord documentTask2 = createDocumentTask(".DocumentTask1");
+        mRecentTasks.add(documentTask1);
+        mRecentTasks.add(documentTask2);
+        assertTrue(mCallbacksRecorder.added.contains(documentTask1));
+        assertTrue(mCallbacksRecorder.added.contains(documentTask2));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.contains(documentTask1));
+    }
+
+    @Test
+    public void testAddTasksMaxTaskRecents_expectNoTrim() throws Exception {
+        // Add a task hitting max-recents for that app will remove the task (to add the next one)
+        // but not trim it
+        TaskRecord documentTask1 = createDocumentTask(".DocumentTask1");
+        TaskRecord documentTask2 = createDocumentTask(".DocumentTask1");
+        documentTask1.maxRecents = 1;
+        documentTask2.maxRecents = 1;
+        mRecentTasks.add(documentTask1);
+        mRecentTasks.add(documentTask2);
+        assertTrue(mCallbacksRecorder.added.contains(documentTask1));
+        assertTrue(mCallbacksRecorder.added.contains(documentTask2));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.contains(documentTask1));
+    }
+
+    @Test
+    public void testAddTasksSameTask_expectNoTrim() throws Exception {
+        // Add a task that is already in the task list does not trigger any callbacks, it just
+        // moves in the list
+        TaskRecord documentTask1 = createDocumentTask(".DocumentTask1");
+        mRecentTasks.add(documentTask1);
+        mRecentTasks.add(documentTask1);
+        assertTrue(mCallbacksRecorder.added.size() == 1);
+        assertTrue(mCallbacksRecorder.added.contains(documentTask1));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.isEmpty());
+    }
+
+    @Test
+    public void testAddTasksMultipleDocumentTasks_expectNoTrim() throws Exception {
+        // Add same multiple-task document tasks does not trim the first tasks
+        TaskRecord documentTask1 = createDocumentTask(".DocumentTask1",
+                FLAG_ACTIVITY_MULTIPLE_TASK);
+        TaskRecord documentTask2 = createDocumentTask(".DocumentTask1",
+                FLAG_ACTIVITY_MULTIPLE_TASK);
+        mRecentTasks.add(documentTask1);
+        mRecentTasks.add(documentTask2);
+        assertTrue(mCallbacksRecorder.added.size() == 2);
+        assertTrue(mCallbacksRecorder.added.contains(documentTask1));
+        assertTrue(mCallbacksRecorder.added.contains(documentTask2));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.isEmpty());
+    }
+
+    @Test
+    public void testAddTasksMultipleTasks_expectRemovedNoTrim() throws Exception {
+        // Add multiple same-affinity non-document tasks, ensure that it removes the other task,
+        // but that it does not trim it
+        TaskRecord task1 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .build();
+        TaskRecord task2 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .build();
+        mRecentTasks.add(task1);
+        assertTrue(mCallbacksRecorder.added.size() == 1);
+        assertTrue(mCallbacksRecorder.added.contains(task1));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.isEmpty());
+        mCallbacksRecorder.clear();
+        mRecentTasks.add(task2);
+        assertTrue(mCallbacksRecorder.added.size() == 1);
+        assertTrue(mCallbacksRecorder.added.contains(task2));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.size() == 1);
+        assertTrue(mCallbacksRecorder.removed.contains(task1));
+    }
+
+    @Test
+    public void testAddTasksDifferentStacks_expectNoRemove() throws Exception {
+        // Adding the same task with different activity types should not trigger removal of the
+        // other task
+        TaskRecord task1 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .setStack(mHomeStack).build();
+        TaskRecord task2 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .setStack(mStack).build();
+        mRecentTasks.add(task1);
+        mRecentTasks.add(task2);
+        assertTrue(mCallbacksRecorder.added.size() == 2);
+        assertTrue(mCallbacksRecorder.added.contains(task1));
+        assertTrue(mCallbacksRecorder.added.contains(task2));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.isEmpty());
+    }
+
+    @Test
+    public void testAddTaskCompatibleActivityType_expectRemove() throws Exception {
+        // Test with undefined activity type since the type is not persisted by the task persister
+        // and we want to ensure that a new task will match a restored task
+        Configuration config1 = new Configuration();
+        config1.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+        TaskRecord task1 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setStack(mStack)
+                .build();
+        task1.onConfigurationChanged(config1);
+        assertTrue(task1.getActivityType() == ACTIVITY_TYPE_UNDEFINED);
+        mRecentTasks.add(task1);
+        mCallbacksRecorder.clear();
+
+        TaskRecord task2 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setStack(mStack)
+                .build();
+        assertTrue(task2.getActivityType() == ACTIVITY_TYPE_STANDARD);
+        mRecentTasks.add(task2);
+        assertTrue(mCallbacksRecorder.added.size() == 1);
+        assertTrue(mCallbacksRecorder.added.contains(task2));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.size() == 1);
+        assertTrue(mCallbacksRecorder.removed.contains(task1));
+    }
+
+    @Test
+    public void testAddTaskCompatibleActivityTypeDifferentUser_expectNoRemove() throws Exception {
+        Configuration config1 = new Configuration();
+        config1.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+        TaskRecord task1 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setStack(mStack)
+                .setUserId(TEST_USER_0_ID)
+                .build();
+        task1.onConfigurationChanged(config1);
+        assertTrue(task1.getActivityType() == ACTIVITY_TYPE_UNDEFINED);
+        mRecentTasks.add(task1);
+        mCallbacksRecorder.clear();
+
+        TaskRecord task2 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setStack(mStack)
+                .setUserId(TEST_USER_1_ID)
+                .build();
+        assertTrue(task2.getActivityType() == ACTIVITY_TYPE_STANDARD);
+        mRecentTasks.add(task2);
+        assertTrue(mCallbacksRecorder.added.size() == 1);
+        assertTrue(mCallbacksRecorder.added.contains(task2));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.isEmpty());
+    }
+
+    @Test
+    public void testAddTaskCompatibleWindowingMode_expectRemove() throws Exception {
+        Configuration config1 = new Configuration();
+        config1.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+        TaskRecord task1 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setStack(mStack)
+                .build();
+        task1.onConfigurationChanged(config1);
+        assertTrue(task1.getWindowingMode() == WINDOWING_MODE_UNDEFINED);
+        mRecentTasks.add(task1);
+        mCallbacksRecorder.clear();
+
+        Configuration config2 = new Configuration();
+        config2.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        TaskRecord task2 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setStack(mStack)
+                .build();
+        task2.onConfigurationChanged(config2);
+        assertTrue(task2.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
+        mRecentTasks.add(task2);
+
+        assertTrue(mCallbacksRecorder.added.size() == 1);
+        assertTrue(mCallbacksRecorder.added.contains(task2));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.size() == 1);
+        assertTrue(mCallbacksRecorder.removed.contains(task1));
+    }
+
+    @Test
+    public void testAddTaskIncompatibleWindowingMode_expectNoRemove() throws Exception {
+        Configuration config1 = new Configuration();
+        config1.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        TaskRecord task1 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setStack(mStack)
+                .build();
+        task1.onConfigurationChanged(config1);
+        assertTrue(task1.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
+        mRecentTasks.add(task1);
+
+        Configuration config2 = new Configuration();
+        config2.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
+        TaskRecord task2 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setStack(mStack)
+                .build();
+        task2.onConfigurationChanged(config2);
+        assertTrue(task2.getWindowingMode() == WINDOWING_MODE_PINNED);
+        mRecentTasks.add(task2);
+
+        assertTrue(mCallbacksRecorder.added.size() == 2);
+        assertTrue(mCallbacksRecorder.added.contains(task1));
+        assertTrue(mCallbacksRecorder.added.contains(task2));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.isEmpty());
+    }
+
+    @Test
+    public void testUsersTasks() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
+
+        // Setup some tasks for the users
+        mTaskPersister.userTaskIdsOverride = new SparseBooleanArray();
+        mTaskPersister.userTaskIdsOverride.put(1, true);
+        mTaskPersister.userTaskIdsOverride.put(2, true);
+        mTaskPersister.userTasksOverride = new ArrayList<>();
+        mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask1").build());
+        mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask2").build());
+
+        // Assert no user tasks are initially loaded
+        assertTrue(mRecentTasks.usersWithRecentsLoadedLocked().length == 0);
+
+        // Load user 0 tasks
+        mRecentTasks.loadUserRecentsLocked(TEST_USER_0_ID);
+        assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID));
+        assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID));
+        assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID));
+
+        // Load user 1 tasks
+        mRecentTasks.loadUserRecentsLocked(TEST_USER_1_ID);
+        assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID));
+        assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID));
+        assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID));
+        assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID));
+        assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_1_ID));
+        assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_1_ID));
+
+        // Unload user 1 tasks
+        mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_1_ID);
+        assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID));
+        assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID));
+        assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID));
+        assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID));
+
+        // Unload user 0 tasks
+        mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_0_ID);
+        assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID));
+        assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID));
+    }
+
+    @Test
+    public void testOrderedIteration() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
+        TaskRecord task1 = createTaskBuilder(".Task1").build();
+        task1.lastActiveTime = new Random().nextInt();
+        TaskRecord task2 = createTaskBuilder(".Task1").build();
+        task2.lastActiveTime = new Random().nextInt();
+        TaskRecord task3 = createTaskBuilder(".Task1").build();
+        task3.lastActiveTime = new Random().nextInt();
+        TaskRecord task4 = createTaskBuilder(".Task1").build();
+        task4.lastActiveTime = new Random().nextInt();
+        mRecentTasks.add(task1);
+        mRecentTasks.add(task2);
+        mRecentTasks.add(task3);
+        mRecentTasks.add(task4);
+
+        MutableLong prevLastActiveTime = new MutableLong(0);
+        final ArrayList<TaskRecord> tasks = mRecentTasks.getRawTasks();
+        for (int i = 0; i < tasks.size(); i++) {
+            final TaskRecord task = tasks.get(i);
+            assertTrue(task.lastActiveTime >= prevLastActiveTime.value);
+            prevLastActiveTime.value = task.lastActiveTime;
+        }
+    }
+
+    @Test
+    public void testTrimToGlobalMaxNumRecents() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
+
+        // Limit the global maximum number of recent tasks to a fixed size
+        mRecentTasks.setGlobalMaxNumTasks(2 /* globalMaxNumTasks */);
+
+        // Add N+1 tasks
+        mRecentTasks.add(mTasks.get(0));
+        mRecentTasks.add(mTasks.get(1));
+        mRecentTasks.add(mTasks.get(2));
+
+        // Ensure that the last task was trimmed as an inactive task
+        assertTrimmed(mTasks.get(0));
+    }
+
+    @Test
+    public void testTrimQuietProfileTasks() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
+        TaskRecord qt1 = createTaskBuilder(".QuietTask1").setUserId(TEST_QUIET_USER_ID).build();
+        TaskRecord qt2 = createTaskBuilder(".QuietTask2").setUserId(TEST_QUIET_USER_ID).build();
+        mRecentTasks.add(qt1);
+        mRecentTasks.add(qt2);
+
+        mRecentTasks.add(mTasks.get(0));
+        mRecentTasks.add(mTasks.get(1));
+
+        // Ensure that the quiet user's tasks was trimmed once the new tasks were added
+        assertTrimmed(qt1, qt2);
+    }
+
+    @Test
+    public void testSessionDuration() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
+        mRecentTasks.setParameters(-1 /* min */, -1 /* max */, 50 /* ms */);
+
+        TaskRecord t1 = createTaskBuilder(".Task1").build();
+        t1.touchActiveTime();
+        mRecentTasks.add(t1);
+
+        // Force a small sleep just beyond the session duration
+        SystemClock.sleep(75);
+
+        TaskRecord t2 = createTaskBuilder(".Task2").build();
+        t2.touchActiveTime();
+        mRecentTasks.add(t2);
+
+        // Assert that the old task has been removed due to being out of the active session
+        assertTrimmed(t1);
+    }
+
+    @Test
+    public void testVisibleTasks_excludedFromRecents() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
+        mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */);
+
+        TaskRecord excludedTask1 = createTaskBuilder(".ExcludedTask1")
+                .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                .build();
+        TaskRecord excludedTask2 = createTaskBuilder(".ExcludedTask2")
+                .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                .build();
+
+        mRecentTasks.add(excludedTask1);
+        mRecentTasks.add(mTasks.get(0));
+        mRecentTasks.add(mTasks.get(1));
+        mRecentTasks.add(mTasks.get(2));
+        mRecentTasks.add(excludedTask2);
+
+        // The last excluded task should be trimmed, while the first-most excluded task should not
+        assertTrimmed(excludedTask1);
+    }
+
+    @Test
+    public void testVisibleTasks_minNum() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
+        mRecentTasks.setParameters(5 /* min */, -1 /* max */, 25 /* ms */);
+
+        for (int i = 0; i < 4; i++) {
+            final TaskRecord task = mTasks.get(i);
+            task.touchActiveTime();
+            mRecentTasks.add(task);
+        }
+
+        // Force a small sleep just beyond the session duration
+        SystemClock.sleep(50);
+
+        // Add a new task to trigger tasks to be trimmed
+        mRecentTasks.add(mTasks.get(4));
+
+        // Ensure that there are a minimum number of tasks regardless of session length
+        assertNoTasksTrimmed();
+    }
+
+    @Test
+    public void testVisibleTasks_maxNum() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
+        mRecentTasks.setParameters(-1 /* min */, 3 /* max */, -1 /* ms */);
+
+        for (int i = 0; i < 5; i++) {
+            final TaskRecord task = mTasks.get(i);
+            task.touchActiveTime();
+            mRecentTasks.add(task);
+        }
+
+        // Ensure that only the last number of max tasks are kept
+        assertTrimmed(mTasks.get(0), mTasks.get(1));
+    }
+
+    @Test
+    public void testBackStackTasks_expectNoTrim() throws Exception {
+        mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
+
+        final MyTestActivityStackSupervisor supervisor =
+                (MyTestActivityStackSupervisor) mService.mStackSupervisor;
+        final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor);
+        final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor);
+        supervisor.setHomeStack(homeStack);
+
+        // Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all
+        // the tasks belong in stacks above the home stack
+        mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task1").setStack(aboveHomeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task3").setStack(aboveHomeStack).build());
+
+        assertNoTasksTrimmed();
+    }
+
+    @Test
+    public void testBehindHomeStackTasks_expectTaskTrimmed() throws Exception {
+        mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
+
+        final MyTestActivityStackSupervisor supervisor =
+                (MyTestActivityStackSupervisor) mService.mStackSupervisor;
+        final ActivityStack behindHomeStack = new MyTestActivityStack(mDisplay, supervisor);
+        final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor);
+        final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor);
+        supervisor.setHomeStack(homeStack);
+
+        // Add a number of tasks (beyond the max) but ensure that only the task in the stack behind
+        // the home stack is trimmed once a new task is added
+        final TaskRecord behindHomeTask = createTaskBuilder(".Task1")
+                .setStack(behindHomeStack)
+                .build();
+        mRecentTasks.add(behindHomeTask);
+        mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build());
+
+        assertTrimmed(behindHomeTask);
+    }
+
+    @Test
+    public void testOtherDisplayTasks_expectNoTrim() throws Exception {
+        mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
+
+        final MyTestActivityStackSupervisor supervisor =
+                (MyTestActivityStackSupervisor) mService.mStackSupervisor;
+        final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor);
+        final ActivityStack otherDisplayStack = new MyTestActivityStack(mOtherDisplay, supervisor);
+        supervisor.setHomeStack(homeStack);
+
+        // Add a number of tasks (beyond the max) on each display, ensure that the tasks are not
+        // removed
+        mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task1").setStack(otherDisplayStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task2").setStack(otherDisplayStack).build());
+        mRecentTasks.add(createTaskBuilder(".HomeTask2").setStack(homeStack).build());
+
+        assertNoTasksTrimmed();
+    }
+
+    @Test
+    public void testRemovePackageByName() throws Exception {
+        // Add a number of tasks with the same package name
+        mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".Task1").build());
+        mRecentTasks.add(createTaskBuilder("com.android.pkg2", ".Task2").build());
+        mRecentTasks.add(createTaskBuilder("com.android.pkg3", ".Task3").build());
+        mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".Task4").build());
+        mRecentTasks.removeTasksByPackageName("com.android.pkg1", TEST_USER_0_ID);
+
+        final ArrayList<TaskRecord> tasks = mRecentTasks.getRawTasks();
+        for (int i = 0; i < tasks.size(); i++) {
+            if (tasks.get(i).intent.getComponent().getPackageName().equals("com.android.pkg1")) {
+                fail("Expected com.android.pkg1 tasks to be removed");
+            }
+        }
+    }
+
+    @Test
+    public void testNotRecentsComponent_denyApiAccess() throws Exception {
+        doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(),
+                anyInt(), anyInt());
+
+        // Expect the following methods to fail due to recents component not being set
+        mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION);
+        testRecentTasksApis(false /* expectNoSecurityException */);
+        // Don't throw for the following tests
+        mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY);
+        testGetTasksApis(false /* expectNoSecurityException */);
+    }
+
+    @Test
+    public void testRecentsComponent_allowApiAccessWithoutPermissions() throws Exception {
+        doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(),
+                anyInt(), anyInt());
+
+        // Set the recents component and ensure that the following calls do not fail
+        mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.GRANT);
+        testRecentTasksApis(true /* expectNoSecurityException */);
+        testGetTasksApis(true /* expectNoSecurityException */);
+    }
+
+    private void testRecentTasksApis(boolean expectCallable) {
+        assertSecurityException(expectCallable, () -> mService.removeStack(INVALID_STACK_ID));
+        assertSecurityException(expectCallable,
+                () -> mService.removeStacksInWindowingModes(new int[] {WINDOWING_MODE_UNDEFINED}));
+        assertSecurityException(expectCallable,
+                () -> mService.removeStacksWithActivityTypes(new int[] {ACTIVITY_TYPE_UNDEFINED}));
+        assertSecurityException(expectCallable, () -> mService.removeTask(0));
+        assertSecurityException(expectCallable,
+                () -> mService.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true));
+        assertSecurityException(expectCallable,
+                () -> mService.moveTaskToStack(0, INVALID_STACK_ID, true));
+        assertSecurityException(expectCallable,
+                () -> mService.setTaskWindowingModeSplitScreenPrimary(0,
+                        SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect(), true));
+        assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true));
+        assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0));
+        assertSecurityException(expectCallable,
+                () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
+        assertSecurityException(expectCallable,
+                () -> mService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0));
+        assertSecurityException(expectCallable,
+                () -> mService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(),
+                        new Rect()));
+        assertSecurityException(expectCallable,
+                () -> mService.resizePinnedStack(new Rect(), new Rect()));
+        assertSecurityException(expectCallable, () -> mService.getAllStackInfos());
+        assertSecurityException(expectCallable,
+                () -> mService.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED));
+        assertSecurityException(expectCallable, () -> {
+            try {
+                mService.getFocusedStackInfo();
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        });
+        assertSecurityException(expectCallable,
+                () -> mService.moveTasksToFullscreenStack(INVALID_STACK_ID, true));
+        assertSecurityException(expectCallable,
+                () -> mService.startActivityFromRecents(0, new Bundle()));
+        assertSecurityException(expectCallable,
+                () -> mService.getTaskSnapshot(0, true));
+        assertSecurityException(expectCallable, () -> {
+            try {
+                mService.registerTaskStackListener(null);
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        });
+        assertSecurityException(expectCallable, () -> {
+            try {
+                mService.unregisterTaskStackListener(null);
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        });
+        assertSecurityException(expectCallable, () -> mService.getTaskDescription(0));
+        assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0));
+        assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null,
+                null));
+        assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation(true));
+        assertSecurityException(expectCallable, () -> mService.stopAppSwitches());
+        assertSecurityException(expectCallable, () -> mService.resumeAppSwitches());
+    }
+
+    private void testGetTasksApis(boolean expectCallable) {
+        mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID);
+        mService.getTasks(MAX_VALUE);
+        if (expectCallable) {
+            assertTrue(mRecentTasks.lastAllowed);
+            assertTrue(mRunningTasks.lastAllowed);
+        } else {
+            assertFalse(mRecentTasks.lastAllowed);
+            assertFalse(mRunningTasks.lastAllowed);
+        }
+    }
+
+    private TaskBuilder createTaskBuilder(String className) {
+        return createTaskBuilder(mContext.getPackageName(), className);
+    }
+
+    private TaskBuilder createTaskBuilder(String packageName, String className) {
+        return new TaskBuilder(mService.mStackSupervisor)
+                .setComponent(new ComponentName(packageName, className))
+                .setStack(mStack)
+                .setTaskId(LAST_TASK_ID++)
+                .setUserId(TEST_USER_0_ID);
+    }
+
+    private TaskRecord createDocumentTask(String className) {
+        return createDocumentTask(className, 0);
+    }
+
+    private TaskRecord createDocumentTask(String className, int flags) {
+        TaskRecord task = createTaskBuilder(className)
+                .setFlags(FLAG_ACTIVITY_NEW_DOCUMENT | flags)
+                .build();
+        task.affinity = null;
+        task.maxRecents = ActivityManager.getMaxAppRecentsLimitStatic();
+        return task;
+    }
+
+    private boolean arrayContainsUser(int[] userIds, int targetUserId) {
+        Arrays.sort(userIds);
+        return Arrays.binarySearch(userIds, targetUserId) >= 0;
+    }
+
+    private void assertNoTasksTrimmed() {
+        assertTrimmed();
+    }
+
+    private void assertTrimmed(TaskRecord... tasks) {
+        final ArrayList<TaskRecord> trimmed = mCallbacksRecorder.trimmed;
+        final ArrayList<TaskRecord> removed = mCallbacksRecorder.removed;
+        assertTrue("Expected " + tasks.length + " trimmed tasks, got " + trimmed.size(),
+                trimmed.size() == tasks.length);
+        assertTrue("Expected " + tasks.length + " removed tasks, got " + removed.size(),
+                removed.size() == tasks.length);
+        for (TaskRecord task : tasks) {
+            assertTrue("Expected trimmed task: " + task, trimmed.contains(task));
+            assertTrue("Expected removed task: " + task, removed.contains(task));
+        }
+    }
+
+    private void assertSecurityException(boolean expectCallable, Runnable runnable) {
+        boolean noSecurityException = true;
+        try {
+            runnable.run();
+        } catch (SecurityException se) {
+            noSecurityException = false;
+        } catch (Exception e) {
+            // We only care about SecurityExceptions, fall through here
+            e.printStackTrace();
+        }
+        if (noSecurityException != expectCallable) {
+            fail("Expected callable: " + expectCallable + " but got no security exception: "
+                    + noSecurityException);
+        }
+    }
+
+    private class MyTestActivityManagerService extends TestActivityManagerService {
+        MyTestActivityManagerService(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected ActivityStackSupervisor createTestSupervisor() {
+            return new MyTestActivityStackSupervisor(this, mHandlerThread.getLooper());
+        }
+
+        @Override
+        protected RecentTasks createRecentTasks() {
+            return new TestRecentTasks(this, mTaskPersister, new TestUserController(this));
+        }
+
+        @Override
+        public boolean isUserRunning(int userId, int flags) {
+            return true;
+        }
+    }
+
+    private class MyTestActivityStackSupervisor extends TestActivityStackSupervisor {
+        public MyTestActivityStackSupervisor(ActivityManagerService service, Looper looper) {
+            super(service, looper);
+        }
+
+        @Override
+        public void initialize() {
+            super.initialize();
+            mDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY);
+            mOtherDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY);
+            attachDisplay(mOtherDisplay);
+            attachDisplay(mDisplay);
+        }
+
+        @Override
+        RunningTasks createRunningTasks() {
+            mRunningTasks = new TestRunningTasks();
+            return mRunningTasks;
+        }
+
+        void setHomeStack(ActivityStack stack) {
+            mHomeStack = stack;
+        }
+    }
+
+    private class MyTestActivityStack extends TestActivityStack {
+        private ActivityDisplay mDisplay = null;
+
+        MyTestActivityStack(ActivityDisplay display, ActivityStackSupervisor supervisor) {
+            super(display, LAST_STACK_ID++, supervisor, WINDOWING_MODE_FULLSCREEN,
+                    ACTIVITY_TYPE_STANDARD, true);
+            mDisplay = display;
+        }
+
+        @Override
+        ActivityDisplay getDisplay() {
+            if (mDisplay != null) {
+                return mDisplay;
+            }
+            return super.getDisplay();
+        }
+    }
+
+    private static class CallbacksRecorder implements Callbacks {
+        ArrayList<TaskRecord> added = new ArrayList<>();
+        ArrayList<TaskRecord> trimmed = new ArrayList<>();
+        ArrayList<TaskRecord> removed = new ArrayList<>();
+
+        void clear() {
+            added.clear();
+            trimmed.clear();
+            removed.clear();
+        }
+
+        @Override
+        public void onRecentTaskAdded(TaskRecord task) {
+            added.add(task);
+        }
+
+        @Override
+        public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
+            if (wasTrimmed) {
+                trimmed.add(task);
+            }
+            removed.add(task);
+        }
+    }
+
+    private static class TestTaskPersister extends TaskPersister {
+        SparseBooleanArray userTaskIdsOverride;
+        ArrayList<TaskRecord> userTasksOverride;
+
+        TestTaskPersister(File workingDir) {
+            super(workingDir);
+        }
+
+        @Override
+        SparseBooleanArray loadPersistedTaskIdsForUser(int userId) {
+            if (userTaskIdsOverride != null) {
+                return userTaskIdsOverride;
+            }
+            return super.loadPersistedTaskIdsForUser(userId);
+        }
+
+        @Override
+        List<TaskRecord> restoreTasksForUserLocked(int userId, SparseBooleanArray preaddedTasks) {
+            if (userTasksOverride != null) {
+                return userTasksOverride;
+            }
+            return super.restoreTasksForUserLocked(userId, preaddedTasks);
+        }
+    }
+
+    private static class TestRecentTasks extends RecentTasks {
+        static final int GRANT = 0;
+        static final int DENY = 1;
+        static final int DENY_THROW_SECURITY_EXCEPTION = 2;
+
+        private boolean mOverrideIsCallerRecents;
+        private boolean mIsTrimmableOverride;
+        private int mIsCallerRecentsPolicy;
+
+        boolean lastAllowed;
+
+        TestRecentTasks(ActivityManagerService service, TaskPersister taskPersister,
+                UserController userController) {
+            super(service, taskPersister, userController);
+        }
+
+        @Override
+        boolean isCallerRecents(int callingUid) {
+            if (mOverrideIsCallerRecents) {
+                switch (mIsCallerRecentsPolicy) {
+                    case GRANT:
+                        return true;
+                    case DENY:
+                        return false;
+                    case DENY_THROW_SECURITY_EXCEPTION:
+                        throw new SecurityException();
+                }
+            }
+            return super.isCallerRecents(callingUid);
+        }
+
+        void setIsCallerRecentsOverride(int policy) {
+            mOverrideIsCallerRecents = true;
+            mIsCallerRecentsPolicy = policy;
+        }
+
+        /**
+         * To simplify the setup for some tests, the caller can request that we only rely on the
+         * visible range test to determine what is trimmable. In this case, we don't try to
+         * use the stack order to determine additionally if the task is trimmable when it is not
+         * in the visible range.
+         */
+        void setOnlyTestVisibleRange() {
+            mIsTrimmableOverride = true;
+        }
+
+        @Override
+        ParceledListSlice<RecentTaskInfo> getRecentTasks(int maxNum, int flags,
+                boolean getTasksAllowed,
+                boolean getDetailedTasks, int userId, int callingUid) {
+            lastAllowed = getTasksAllowed;
+            return super.getRecentTasks(maxNum, flags, getTasksAllowed, getDetailedTasks, userId,
+                    callingUid);
+        }
+
+        @Override
+        protected boolean isTrimmable(TaskRecord task) {
+            return mIsTrimmableOverride || super.isTrimmable(task);
+        }
+    }
+
+    private static class TestRunningTasks extends RunningTasks {
+        boolean lastAllowed;
+
+        @Override
+        void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType,
+                int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
+                int callingUid, boolean allowed) {
+            lastAllowed = allowed;
+            super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays,
+                    callingUid, allowed);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java b/services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java
new file mode 100644
index 0000000..eefd973
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 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_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.IRecentsAnimationRunner;
+import com.android.server.AttributeCache;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * atest FrameworksServicesTests:RecentsAnimationTest
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RecentsAnimationTest extends ActivityTestsBase {
+    private static final int TEST_CALLING_PID = 3;
+
+    private Context mContext = InstrumentationRegistry.getContext();
+    private ActivityManagerService mService;
+    private ComponentName mRecentsComponent;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mRecentsComponent = new ComponentName(mContext.getPackageName(), "RecentsActivity");
+        mService = setupActivityManagerService(new MyTestActivityManagerService(mContext));
+        AttributeCache.init(mContext);
+    }
+
+    @Test
+    public void testCancelAnimationOnStackOrderChange() throws Exception {
+        ActivityStack fullscreenStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        ActivityStack recentsStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */);
+        ActivityRecord recentsActivity = new ActivityBuilder(mService)
+                .setComponent(mRecentsComponent)
+                .setCreateTask(true)
+                .setStack(recentsStack)
+                .build();
+        ActivityStack fullscreenStack2 = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        ActivityRecord fsActivity = new ActivityBuilder(mService)
+                .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
+                .setCreateTask(true)
+                .setStack(fullscreenStack2)
+                .build();
+        doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation();
+
+        // Start the recents animation
+        Intent recentsIntent = new Intent();
+        recentsIntent.setComponent(mRecentsComponent);
+        mService.startRecentsActivity(recentsIntent, null, mock(IRecentsAnimationRunner.class));
+
+        fullscreenStack.moveToFront("Activity start");
+
+        // Ensure that the recents animation was canceled
+        verify(mService.mWindowManager, times(1)).cancelRecentsAnimationSynchronously(
+                eq(REORDER_KEEP_IN_PLACE), any());
+    }
+
+    private class MyTestActivityManagerService extends TestActivityManagerService {
+        MyTestActivityManagerService(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected RecentTasks createRecentTasks() {
+            RecentTasks recents = mock(RecentTasks.class);
+            doReturn(mRecentsComponent).when(recents).getRecentsComponent();
+            System.out.println(mRecentsComponent);
+            return recents;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
new file mode 100644
index 0000000..c6ce7e1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 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.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+/**
+ * runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RunningTasksTest extends ActivityTestsBase {
+
+    private Context mContext = InstrumentationRegistry.getContext();
+    private ActivityManagerService mService;
+
+    private RunningTasks mRunningTasks;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mService = createActivityManagerService();
+        mRunningTasks = new RunningTasks();
+    }
+
+    @Test
+    public void testCollectTasksByLastActiveTime() throws Exception {
+        // Create a number of stacks with tasks (of incrementing active time)
+        final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
+        final SparseArray<ActivityDisplay> displays = new SparseArray<>();
+        final ActivityDisplay display = new TestActivityDisplay(supervisor, DEFAULT_DISPLAY);
+        displays.put(DEFAULT_DISPLAY, display);
+
+        final int numStacks = 2;
+        for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) {
+            final ActivityStack stack = new TestActivityStack(display, stackIndex, supervisor,
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true);
+            display.addChild(stack, POSITION_BOTTOM);
+        }
+
+        final int numTasks = 10;
+        int activeTime = 0;
+        for (int i = 0; i < numTasks; i++) {
+            createTask(display.getChildAt(i % numStacks), ".Task" + i, i, activeTime++);
+        }
+
+        // Ensure that the latest tasks were returned in order of decreasing last active time,
+        // collected from all tasks across all the stacks
+        final int numFetchTasks = 5;
+        ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
+        mRunningTasks.getTasks(5, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED,
+                displays, -1 /* callingUid */, true /* allowed */);
+        assertTrue(tasks.size() == numFetchTasks);
+        for (int i = 0; i < numFetchTasks; i++) {
+            assertTrue(tasks.get(i).id == (numTasks - i - 1));
+        }
+
+        // Ensure that requesting more than the total number of tasks only returns the subset
+        // and does not crash
+        tasks.clear();
+        mRunningTasks.getTasks(100, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED,
+                displays, -1 /* callingUid */, true /* allowed */);
+        assertTrue(tasks.size() == numTasks);
+        for (int i = 0; i < numTasks; i++) {
+            assertTrue(tasks.get(i).id == (numTasks - i - 1));
+        }
+    }
+
+    /**
+     * Create a task with a single activity in it, with the given last active time.
+     */
+    private TaskRecord createTask(ActivityStack stack, String className, int taskId,
+            int lastActiveTime) {
+        final TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
+                .setComponent(new ComponentName(mContext.getPackageName(), className))
+                .setTaskId(taskId)
+                .setStack(stack)
+                .build();
+        task.lastActiveTime = lastActiveTime;
+        final ActivityRecord activity = new ActivityBuilder(mService)
+                .setTask(task)
+                .setComponent(new ComponentName(mContext.getPackageName(), ".TaskActivity"))
+                .build();
+        return task;
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/SafeActivityOptionsTest.java b/services/tests/servicestests/src/com/android/server/am/SafeActivityOptionsTest.java
new file mode 100644
index 0000000..168bc17
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/SafeActivityOptionsTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+
+import android.app.ActivityOptions;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@Presubmit
+@FlakyTest
+@RunWith(AndroidJUnit4.class)
+public class SafeActivityOptionsTest {
+
+    @Test
+    public void testMerge() {
+        final ActivityOptions opts1 = ActivityOptions.makeBasic();
+        opts1.setLaunchDisplayId(5);
+        final ActivityOptions opts2 = ActivityOptions.makeBasic();
+        opts2.setLaunchDisplayId(6);
+        final SafeActivityOptions options = new SafeActivityOptions(opts1);
+        final ActivityOptions result = options.mergeActivityOptions(opts1, opts2);
+        assertEquals(6, result.getLaunchDisplayId());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java b/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java
new file mode 100644
index 0000000..3d323f0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2017 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 android.content.pm.ActivityInfo.WindowLayout;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.view.Gravity;
+
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.mockito.invocation.InvocationOnMock;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
+import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.doAnswer;
+import static org.junit.Assert.assertEquals;
+
+
+/**
+ * Tests for exercising resizing task bounds.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:TaskLaunchParamsModifierTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskLaunchParamsModifierTests extends ActivityTestsBase {
+    private final static int STACK_WIDTH = 100;
+    private final static int STACK_HEIGHT = 200;
+
+    private final static Rect STACK_BOUNDS = new Rect(0, 0, STACK_WIDTH, STACK_HEIGHT);
+
+    private ActivityManagerService mService;
+    private ActivityStack mStack;
+    private TaskRecord mTask;
+
+    private TaskLaunchParamsModifier mPositioner;
+
+    private LaunchParamsController.LaunchParams mCurrent;
+    private LaunchParamsController.LaunchParams mResult;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mService = createActivityManagerService();
+        mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        mStack.requestResize(STACK_BOUNDS);
+
+        // We must create the task after resizing to make sure it does not inherit the stack
+        // dimensions on resize.
+        mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+
+        mPositioner = new TaskLaunchParamsModifier();
+
+        mResult = new LaunchParamsController.LaunchParams();
+        mCurrent = new LaunchParamsController.LaunchParams();
+    }
+
+    /**
+     * Ensures that the setup bounds are set as expected with the stack bounds set and the task
+     * bounds still {@code null}.
+     * @throws Exception
+     */
+    @Test
+    public void testInitialBounds() throws Exception {
+        assertEquals(mStack.getOverrideBounds(), STACK_BOUNDS);
+        assertEquals(mTask.getOverrideBounds(), new Rect());
+    }
+
+    /**
+     * Ensures that a task positioned with no {@link WindowLayout} receives the default launch
+     * position.
+     * @throws Exception
+     */
+    @Test
+    public void testLaunchNoWindowLayout() throws Exception {
+        assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask, null /*layout*/,
+                null /*record*/, null /*source*/, null /*options*/, mCurrent, mResult));
+        assertEquals(getDefaultBounds(Gravity.NO_GRAVITY), mResult.mBounds);
+    }
+
+    /**
+     * Ensures that a task positioned with an empty {@link WindowLayout} receives the default launch
+     * position.
+     * @throws Exception
+     */
+    @Test
+    public void testlaunchEmptyWindowLayout() throws Exception {
+        assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask,
+                new WindowLayout(0, 0, 0, 0, Gravity.NO_GRAVITY, 0, 0), null /*activity*/,
+                null /*source*/, null /*options*/, mCurrent, mResult));
+        assertEquals(mResult.mBounds, getDefaultBounds(Gravity.NO_GRAVITY));
+    }
+
+    /**
+     * Ensures that a task positioned with a {@link WindowLayout} gravity specified is positioned
+     * according to specification.
+     * @throws Exception
+     */
+    @Test
+    public void testlaunchWindowLayoutGravity() throws Exception {
+        // Unspecified gravity should be ignored
+        testGravity(Gravity.NO_GRAVITY);
+
+        // Unsupported gravity should be ignored
+        testGravity(Gravity.LEFT);
+        testGravity(Gravity.RIGHT);
+
+        // Test defaults for vertical gravities
+        testGravity(Gravity.TOP);
+        testGravity(Gravity.BOTTOM);
+
+        // Test corners
+        testGravity(Gravity.TOP | Gravity.LEFT);
+        testGravity(Gravity.TOP | Gravity.RIGHT);
+        testGravity(Gravity.BOTTOM | Gravity.LEFT);
+        testGravity(Gravity.BOTTOM | Gravity.RIGHT);
+    }
+
+    private void testGravity(int gravity) {
+        try {
+            assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask,
+                    new WindowLayout(0, 0, 0, 0, gravity, 0, 0), null /*activity*/,
+                    null /*source*/, null /*options*/, mCurrent, mResult));
+            assertEquals(mResult.mBounds, getDefaultBounds(gravity));
+        } finally {
+            mCurrent.reset();
+            mResult.reset();
+        }
+    }
+
+    /**
+     * Ensures that a task which causes a conflict with another task when positioned is adjusted as
+     * expected.
+     * @throws Exception
+     */
+    @Test
+    public void testLaunchWindowCenterConflict() throws Exception {
+        testConflict(Gravity.NO_GRAVITY);
+        testConflict(Gravity.TOP);
+        testConflict(Gravity.BOTTOM);
+        testConflict(Gravity.TOP | Gravity.LEFT);
+        testConflict(Gravity.TOP | Gravity.RIGHT);
+        testConflict(Gravity.BOTTOM | Gravity.LEFT);
+        testConflict(Gravity.BOTTOM | Gravity.RIGHT);
+    }
+
+    private void testConflict(int gravity) {
+        final WindowLayout layout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0);
+
+        // layout first task
+        mService.mStackSupervisor.getLaunchParamsController().layoutTask(mTask, layout);
+
+        // Second task will be laid out on top of the first so starting bounds is the same.
+        final Rect expectedBounds = new Rect(mTask.getOverrideBounds());
+
+        ActivityRecord activity = null;
+        TaskRecord secondTask = null;
+
+        // wrap with try/finally to ensure cleanup of activity/stack.
+        try {
+            // empty tasks are ignored in conflicts
+            activity = new ActivityBuilder(mService).setTask(mTask).build();
+
+            // Create secondary task
+            secondTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+
+            // layout second task
+            assertEquals(RESULT_CONTINUE,
+                    mPositioner.onCalculate(secondTask, layout, null /*activity*/,
+                            null /*source*/, null /*options*/, mCurrent, mResult));
+
+            if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)
+                    || (gravity & (Gravity.BOTTOM | Gravity.RIGHT))
+                    == (Gravity.BOTTOM | Gravity.RIGHT)) {
+                expectedBounds.offset(-TaskLaunchParamsModifier.getHorizontalStep(
+                        mStack.getOverrideBounds()), 0);
+            } else if ((gravity & Gravity.TOP) == Gravity.TOP
+                    || (gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
+                expectedBounds.offset(
+                        TaskLaunchParamsModifier.getHorizontalStep(mStack.getOverrideBounds()), 0);
+            } else {
+                expectedBounds.offset(
+                        TaskLaunchParamsModifier.getHorizontalStep(mStack.getOverrideBounds()),
+                        TaskLaunchParamsModifier.getVerticalStep(mStack.getOverrideBounds()));
+            }
+
+            assertEquals(mResult.mBounds, expectedBounds);
+        } finally {
+            // Remove task and activity to prevent influencing future tests
+            if (activity != null) {
+                mTask.removeActivity(activity);
+            }
+
+            if (secondTask != null) {
+                mStack.removeTask(secondTask, "cleanup", ActivityStack.REMOVE_TASK_MODE_DESTROYING);
+            }
+        }
+    }
+
+    private Rect getDefaultBounds(int gravity) {
+        final Rect bounds = new Rect();
+        bounds.set(mStack.getOverrideBounds());
+
+        final int verticalInset =
+                TaskLaunchParamsModifier.getFreeformStartTop(mStack.getOverrideBounds());
+        final int horizontalInset =
+                TaskLaunchParamsModifier.getFreeformStartLeft(mStack.getOverrideBounds());
+
+        bounds.inset(horizontalInset, verticalInset);
+
+        if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)) {
+            bounds.offsetTo(horizontalInset * 2, 0);
+        } else if ((gravity & Gravity.TOP) == Gravity.TOP) {
+            bounds.offsetTo(0, 0);
+        } else if ((gravity & (Gravity.BOTTOM | Gravity.RIGHT))
+                == (Gravity.BOTTOM | Gravity.RIGHT)) {
+            bounds.offsetTo(horizontalInset * 2, verticalInset * 2);
+        } else if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
+            bounds.offsetTo(0, verticalInset * 2);
+        }
+
+        return bounds;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java b/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java
new file mode 100644
index 0000000..9e6055d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 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 android.content.pm.UserInfo;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+
+import com.android.server.am.TaskPersister;
+
+import java.io.File;
+import java.util.Random;
+
+/**
+ * atest FrameworksServicesTests:TaskPersisterTest
+ */
+public class TaskPersisterTest extends AndroidTestCase {
+    private static final String TEST_USER_NAME = "AM-Test-User";
+
+    private TaskPersister mTaskPersister;
+    private int testUserId;
+    private UserManager mUserManager;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mUserManager = UserManager.get(getContext());
+        mTaskPersister = new TaskPersister(getContext().getFilesDir());
+        testUserId = createUser(TEST_USER_NAME, 0);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        mTaskPersister.unloadUserDataFromMemory(testUserId);
+        removeUser(testUserId);
+    }
+
+    private int getRandomTaskIdForUser(int userId) {
+        int taskId = (int) (Math.random() * UserHandle.PER_USER_RANGE);
+        taskId += UserHandle.PER_USER_RANGE * userId;
+        return taskId;
+    }
+
+    public void testTaskIdsPersistence() {
+        SparseBooleanArray taskIdsOnFile = new SparseBooleanArray();
+        for (int i = 0; i < 100; i++) {
+            taskIdsOnFile.put(getRandomTaskIdForUser(testUserId), true);
+        }
+        mTaskPersister.writePersistedTaskIdsForUser(taskIdsOnFile, testUserId);
+        SparseBooleanArray newTaskIdsOnFile = mTaskPersister
+                .loadPersistedTaskIdsForUser(testUserId);
+        assertTrue("TaskIds written differ from TaskIds read back from file",
+                taskIdsOnFile.equals(newTaskIdsOnFile));
+    }
+
+    private int createUser(String name, int flags) {
+        UserInfo user = mUserManager.createUser(name, flags);
+        if (user == null) {
+            fail("Error while creating the test user: " + TEST_USER_NAME);
+        }
+        return user.id;
+    }
+
+    private void removeUser(int userId) {
+        if (!mUserManager.removeUser(userId)) {
+            fail("Error while removing the test user: " + TEST_USER_NAME);
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/TaskRecordTests.java b/services/tests/servicestests/src/com/android/server/am/TaskRecordTests.java
new file mode 100644
index 0000000..057fdc8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/TaskRecordTests.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2017 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.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.XmlResourceParser;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.service.voice.IVoiceInteractionSession;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Xml;
+
+import com.android.frameworks.servicestests.R;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.server.am.TaskRecord.TaskRecordFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Comparator;
+
+/**
+ * Tests for exercising {@link TaskRecord}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:com.android.server.am.TaskRecordTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskRecordTests extends ActivityTestsBase {
+
+    private static final String TASK_TAG = "task";
+
+    private ActivityManagerService mService;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        TaskRecord.setTaskRecordFactory(null);
+        mService = createActivityManagerService();
+    }
+
+    @Test
+    public void testRestoreWindowedTask() throws Exception {
+        final TaskRecord expected = createTaskRecord(64);
+        expected.mLastNonFullscreenBounds = new Rect(50, 50, 100, 100);
+
+        final File serializedFile = serializeToFile(expected);
+
+        try {
+            final TaskRecord actual = restoreFromFile(serializedFile);
+            assertEquals(expected.taskId, actual.taskId);
+            assertEquals(expected.mLastNonFullscreenBounds, actual.mLastNonFullscreenBounds);
+        } finally {
+            serializedFile.delete();
+        }
+    }
+
+    @Test
+    public void testDefaultTaskFactoryNotNull() throws Exception {
+        assertNotNull(TaskRecord.getTaskRecordFactory());
+    }
+
+    @Test
+    public void testCreateTestRecordUsingCustomizedFactory() throws Exception {
+        TestTaskRecordFactory factory = new TestTaskRecordFactory();
+        TaskRecord.setTaskRecordFactory(factory);
+
+        assertFalse(factory.mCreated);
+
+        TaskRecord.create(null, 0, null, null, null, null);
+
+        assertTrue(factory.mCreated);
+    }
+
+    @Test
+    public void testReturnsToHomeStack() throws Exception {
+        final TaskRecord task = createTaskRecord(1);
+        assertFalse(task.returnsToHomeStack());
+        task.intent = null;
+        assertFalse(task.returnsToHomeStack());
+        task.intent = new Intent();
+        assertFalse(task.returnsToHomeStack());
+        task.intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME);
+        assertTrue(task.returnsToHomeStack());
+    }
+
+    private File serializeToFile(TaskRecord r) throws IOException, XmlPullParserException {
+        final File tmpFile = File.createTempFile(r.taskId + "_task_", "xml");
+
+        try (final OutputStream os = new FileOutputStream(tmpFile)) {
+            final XmlSerializer serializer = Xml.newSerializer();
+            serializer.setOutput(os, "UTF-8");
+            serializer.startDocument(null, true);
+            serializer.startTag(null, TASK_TAG);
+            r.saveToXml(serializer);
+            serializer.endTag(null, TASK_TAG);
+            serializer.endDocument();
+        }
+
+        return tmpFile;
+    }
+
+    private TaskRecord restoreFromFile(File file) throws IOException, XmlPullParserException {
+        try (final Reader reader = new BufferedReader(new FileReader(file))) {
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(reader);
+            assertEquals(XmlPullParser.START_TAG, parser.next());
+            assertEquals(TASK_TAG, parser.getName());
+            return TaskRecord.restoreFromXml(parser, mService.mStackSupervisor);
+        }
+    }
+
+    private TaskRecord createTaskRecord(int taskId) {
+        return new TaskRecord(mService, taskId, new Intent(), null, null, null, null, null, false,
+                false, false, 0, 10050, null, new ArrayList<>(), 0, false, null, 0, 0, 0, 0, 0,
+                null, 0, false, false, false, 0, 0);
+    }
+
+    private static class TestTaskRecordFactory extends TaskRecordFactory {
+        private boolean mCreated = false;
+
+        @Override
+        TaskRecord create(ActivityManagerService service, int taskId, ActivityInfo info,
+                Intent intent,
+                IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) {
+            mCreated = true;
+            return null;
+        }
+
+        @Override
+        TaskRecord create(ActivityManagerService service, int taskId, ActivityInfo info,
+                Intent intent,
+                ActivityManager.TaskDescription taskDescription) {
+            mCreated = true;
+            return null;
+        }
+
+        @Override
+        TaskRecord create(ActivityManagerService service, int taskId, Intent intent,
+                Intent affinityIntent, String affinity, String rootAffinity,
+                ComponentName realActivity,
+                ComponentName origActivity, boolean rootWasReset, boolean autoRemoveRecents,
+                boolean askedCompatMode, int userId, int effectiveUid, String lastDescription,
+                ArrayList<ActivityRecord> activities, long lastTimeMoved,
+                boolean neverRelinquishIdentity,
+                ActivityManager.TaskDescription lastTaskDescription,
+                int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor,
+                int callingUid, String callingPackage, int resizeMode,
+                boolean supportsPictureInPicture,
+                boolean realActivitySuspended, boolean userSetupComplete, int minWidth,
+                int minHeight) {
+            mCreated = true;
+            return null;
+        }
+
+        @Override
+        TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor)
+                throws IOException, XmlPullParserException {
+            mCreated = true;
+            return null;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java b/services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java
new file mode 100644
index 0000000..0359096
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2016 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.support.test.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManager.TaskDescription;
+import android.app.IActivityManager;
+import android.app.ITaskStackListener;
+import android.app.Instrumentation.ActivityMonitor;
+import android.app.TaskStackListener;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources.Theme;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
+import android.util.Pair;
+import com.android.internal.annotations.GuardedBy;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class TaskStackChangedListenerTest {
+
+    private IActivityManager mService;
+    private ITaskStackListener mTaskStackListener;
+
+    private static final Object sLock = new Object();
+    @GuardedBy("sLock")
+    private static boolean sTaskStackChangedCalled;
+    private static boolean sActivityBResumed;
+
+    @Before
+    public void setUp() throws Exception {
+        mService = ActivityManager.getService();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mService.unregisterTaskStackListener(mTaskStackListener);
+        mTaskStackListener = null;
+    }
+
+    @Test
+    public void testTaskStackChanged_afterFinish() throws Exception {
+        registerTaskStackChangedListener(new TaskStackListener() {
+            @Override
+            public void onTaskStackChanged() throws RemoteException {
+                synchronized (sLock) {
+                    sTaskStackChangedCalled = true;
+                }
+            }
+        });
+
+        Context ctx = InstrumentationRegistry.getContext();
+        ctx.startActivity(new Intent(ctx, ActivityA.class));
+        UiDevice.getInstance(getInstrumentation()).waitForIdle();
+        synchronized (sLock) {
+            Assert.assertTrue(sTaskStackChangedCalled);
+        }
+        Assert.assertTrue(sActivityBResumed);
+    }
+
+    @Test
+    public void testTaskDescriptionChanged() throws Exception {
+        final Object[] params = new Object[2];
+        final CountDownLatch latch = new CountDownLatch(1);
+        registerTaskStackChangedListener(new TaskStackListener() {
+            int taskId = -1;
+
+            @Override
+            public void onTaskCreated(int taskId, ComponentName componentName)
+                    throws RemoteException {
+                this.taskId = taskId;
+            }
+            @Override
+            public void onTaskDescriptionChanged(int taskId, TaskDescription td)
+                    throws RemoteException {
+                if (this.taskId == taskId && !TextUtils.isEmpty(td.getLabel())) {
+                    params[0] = taskId;
+                    params[1] = td;
+                    latch.countDown();
+                }
+            }
+        });
+        final Activity activity = startTestActivity(ActivityTaskDescriptionChange.class);
+        waitForCallback(latch);
+        assertEquals(activity.getTaskId(), params[0]);
+        assertEquals("Test Label", ((TaskDescription) params[1]).getLabel());
+    }
+
+    @Test
+    public void testActivityRequestedOrientationChanged() throws Exception {
+        final int[] params = new int[2];
+        final CountDownLatch latch = new CountDownLatch(1);
+        registerTaskStackChangedListener(new TaskStackListener() {
+            @Override
+            public void onActivityRequestedOrientationChanged(int taskId,
+                    int requestedOrientation) {
+                params[0] = taskId;
+                params[1] = requestedOrientation;
+                latch.countDown();
+            }
+        });
+        final Activity activity = startTestActivity(ActivityRequestedOrientationChange.class);
+        waitForCallback(latch);
+        assertEquals(activity.getTaskId(), params[0]);
+        assertEquals(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, params[1]);
+    }
+
+    @Test
+    /**
+     * Tests for onTaskCreated, onTaskMovedToFront, onTaskRemoved and onTaskRemovalStarted.
+     */
+    public void testTaskChangeCallBacks() throws Exception {
+        final Object[] params = new Object[2];
+        final CountDownLatch taskCreatedLaunchLatch = new CountDownLatch(1);
+        final CountDownLatch taskMovedToFrontLatch = new CountDownLatch(1);
+        final CountDownLatch taskRemovedLatch = new CountDownLatch(1);
+        final CountDownLatch taskRemovalStartedLatch = new CountDownLatch(1);
+        final CountDownLatch onDetachedFromWindowLatch = new CountDownLatch(1);
+        registerTaskStackChangedListener(new TaskStackListener() {
+            @Override
+            public void onTaskCreated(int taskId, ComponentName componentName)
+                    throws RemoteException {
+                params[0] = taskId;
+                params[1] = componentName;
+                taskCreatedLaunchLatch.countDown();
+            }
+
+            @Override
+            public void onTaskMovedToFront(int taskId) throws RemoteException {
+                params[0] = taskId;
+                taskMovedToFrontLatch.countDown();
+            }
+
+            @Override
+            public void onTaskRemovalStarted(int taskId) {
+                params[0] = taskId;
+                taskRemovalStartedLatch.countDown();
+            }
+
+            @Override
+            public void onTaskRemoved(int taskId) throws RemoteException {
+                params[0] = taskId;
+                taskRemovedLatch.countDown();
+            }
+        });
+
+        final ActivityTaskChangeCallbacks activity =
+                (ActivityTaskChangeCallbacks) startTestActivity(ActivityTaskChangeCallbacks.class);
+        final int id = activity.getTaskId();
+
+        // Test for onTaskCreated.
+        waitForCallback(taskCreatedLaunchLatch);
+        assertEquals(id, params[0]);
+        ComponentName componentName = (ComponentName) params[1];
+        assertEquals(ActivityTaskChangeCallbacks.class.getName(), componentName.getClassName());
+
+        // Test for onTaskMovedToFront.
+        assertEquals(1, taskMovedToFrontLatch.getCount());
+        mService.moveTaskToFront(id, 0, null);
+        waitForCallback(taskMovedToFrontLatch);
+        assertEquals(activity.getTaskId(), params[0]);
+
+        // Test for onTaskRemovalStarted.
+        assertEquals(1, taskRemovalStartedLatch.getCount());
+        activity.finishAndRemoveTask();
+        waitForCallback(taskRemovalStartedLatch);
+        // onTaskRemovalStarted happens before the activity's window is removed.
+        assertFalse(activity.onDetachedFromWindowCalled);
+        assertEquals(id, params[0]);
+
+        // Test for onTaskRemoved.
+        assertEquals(1, taskRemovedLatch.getCount());
+        waitForCallback(taskRemovedLatch);
+        assertEquals(id, params[0]);
+        assertTrue(activity.onDetachedFromWindowCalled);
+    }
+
+    /**
+     * Starts the provided activity and returns the started instance.
+     */
+    private Activity startTestActivity(Class<?> activityClass) {
+        final Context context = InstrumentationRegistry.getContext();
+        final ActivityMonitor monitor =
+                new ActivityMonitor(activityClass.getName(), null, false);
+        InstrumentationRegistry.getInstrumentation().addMonitor(monitor);
+        context.startActivity(new Intent(context, activityClass));
+        final Activity activity = monitor.waitForActivityWithTimeout(1000);
+        if (activity == null) {
+            throw new RuntimeException("Timed out waiting for Activity");
+        }
+        return activity;
+    }
+
+    private void registerTaskStackChangedListener(ITaskStackListener listener) throws Exception {
+        mTaskStackListener = listener;
+        mService.registerTaskStackListener(listener);
+    }
+
+    private void waitForCallback(CountDownLatch latch) {
+        try {
+        final boolean result = latch.await(2, TimeUnit.SECONDS);
+            if (!result) {
+                throw new RuntimeException("Timed out waiting for task stack change notification");
+            }
+        }catch (InterruptedException e) {}
+    }
+
+    public static class ActivityA extends Activity {
+
+        private boolean mActivityBLaunched = false;
+
+        @Override
+        protected void onPostResume() {
+            super.onPostResume();
+            if (mActivityBLaunched) {
+                return;
+            }
+            mActivityBLaunched = true;
+            finish();
+            startActivity(new Intent(this, ActivityB.class));
+        }
+    }
+
+    public static class ActivityB extends Activity {
+
+        @Override
+        protected void onPostResume() {
+            super.onPostResume();
+            synchronized (sLock) {
+                sTaskStackChangedCalled = false;
+            }
+            sActivityBResumed = true;
+            finish();
+        }
+    }
+
+    public static class ActivityRequestedOrientationChange extends Activity {
+        @Override
+        protected void onPostResume() {
+            super.onPostResume();
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+            finish();
+        }
+    }
+
+    public static class ActivityTaskDescriptionChange extends Activity {
+        @Override
+        protected void onPostResume() {
+            super.onPostResume();
+            setTaskDescription(new TaskDescription("Test Label"));
+            finish();
+        }
+    }
+
+    public static class ActivityTaskChangeCallbacks extends Activity {
+        public boolean onDetachedFromWindowCalled = false;;
+
+        @Override
+        public void onDetachedFromWindow() {
+            onDetachedFromWindowCalled = true;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
new file mode 100644
index 0000000..cc4f519
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2016 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 android.app.IUserSwitchObserver;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserManagerInternal;
+import android.platform.test.annotations.Presubmit;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import com.android.server.pm.UserManagerService;
+import com.android.server.wm.WindowManagerService;
+
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
+import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG;
+import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG;
+import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG;
+import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG;
+import static com.android.server.am.UserController.SYSTEM_USER_CURRENT_MSG;
+import static com.android.server.am.UserController.SYSTEM_USER_START_MSG;
+import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+/**
+ * Usage: bit FrameworksServicesTests:com.android.server.am.UserControllerTest
+ */
+@Presubmit
+public class UserControllerTest extends AndroidTestCase {
+    private static final int TEST_USER_ID = 10;
+    private static final int NONEXIST_USER_ID = 2;
+    private static String TAG = UserControllerTest.class.getSimpleName();
+    private UserController mUserController;
+    private TestInjector mInjector;
+
+    private static final List<String> START_FOREGROUND_USER_ACTIONS =
+            Arrays.asList(
+                    Intent.ACTION_USER_STARTED,
+                    Intent.ACTION_USER_SWITCHED,
+                    Intent.ACTION_USER_STARTING);
+
+    private static final List<String> START_BACKGROUND_USER_ACTIONS =
+            Arrays.asList(
+                    Intent.ACTION_USER_STARTED,
+                    Intent.ACTION_LOCKED_BOOT_COMPLETED,
+                    Intent.ACTION_USER_STARTING);
+
+    private static final Set<Integer> START_FOREGROUND_USER_MESSAGE_CODES =
+            new HashSet<>(Arrays.asList(REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG,
+                    SYSTEM_USER_START_MSG, SYSTEM_USER_CURRENT_MSG));
+
+    private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES =
+            new HashSet<>(Arrays.asList(SYSTEM_USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG));
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        runWithDexmakerShareClassLoader(() -> {
+            mInjector = Mockito.spy(new TestInjector(getContext()));
+            doNothing().when(mInjector).clearAllLockedTasks(anyString());
+            doNothing().when(mInjector).startHomeActivity(anyInt(), anyString());
+            doReturn(false).when(mInjector).stackSupervisorSwitchUser(anyInt(), any());
+            doNothing().when(mInjector).stackSupervisorResumeFocusedStackTopActivity();
+            mUserController = new UserController(mInjector);
+            setUpUser(TEST_USER_ID, 0);
+        });
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mInjector.handlerThread.quit();
+        Mockito.validateMockitoUsage();
+    }
+
+    @SmallTest
+    public void testStartUser_foreground() throws RemoteException {
+        mUserController.startUser(TEST_USER_ID, true /* foreground */);
+        Mockito.verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt());
+        Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
+        Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
+        Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(true);
+        Mockito.verify(mInjector).clearAllLockedTasks(anyString());
+        startForegroundUserAssertions();
+    }
+
+    @SmallTest
+    public void testStartUser_background() throws RemoteException {
+        mUserController.startUser(TEST_USER_ID, false /* foreground */);
+        Mockito.verify(
+                mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
+        Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
+        Mockito.verify(mInjector, never()).clearAllLockedTasks(anyString());
+        startBackgroundUserAssertions();
+    }
+
+    @SmallTest
+    public void testStartUserUIDisabled() throws RemoteException {
+        mUserController.mUserSwitchUiEnabled = false;
+        mUserController.startUser(TEST_USER_ID, true /* foreground */);
+        Mockito.verify(mInjector.getWindowManager(), never())
+                .startFreezingScreen(anyInt(), anyInt());
+        Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
+        Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
+        startForegroundUserAssertions();
+    }
+
+    private void startUserAssertions(
+            List<String> expectedActions, Set<Integer> expectedMessageCodes)
+            throws RemoteException {
+        assertEquals(expectedActions, getActions(mInjector.sentIntents));
+        Set<Integer> actualCodes = mInjector.handler.getMessageCodes();
+        assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes);
+    }
+
+    private void startBackgroundUserAssertions() throws RemoteException {
+        startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES);
+    }
+
+    private void startForegroundUserAssertions() throws RemoteException {
+        startUserAssertions(START_FOREGROUND_USER_ACTIONS, START_FOREGROUND_USER_MESSAGE_CODES);
+        Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG);
+        assertNotNull(reportMsg);
+        UserState userState = (UserState) reportMsg.obj;
+        assertNotNull(userState);
+        assertEquals(TEST_USER_ID, userState.mHandle.getIdentifier());
+        assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state);
+        assertEquals("Unexpected old user id", 0, reportMsg.arg1);
+        assertEquals("Unexpected new user id", TEST_USER_ID, reportMsg.arg2);
+    }
+
+    @SmallTest
+    public void testFailedStartUserInForeground() throws RemoteException {
+        mUserController.mUserSwitchUiEnabled = false;
+        mUserController.startUserInForeground(NONEXIST_USER_ID);
+        Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
+        Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(false);
+    }
+
+    @SmallTest
+    public void testDispatchUserSwitch() throws RemoteException {
+        // Prepare mock observer and register it
+        IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
+        when(observer.asBinder()).thenReturn(new Binder());
+        doAnswer(invocation -> {
+            IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1];
+            callback.sendResult(null);
+            return null;
+        }).when(observer).onUserSwitching(anyInt(), any());
+        mUserController.registerUserSwitchObserver(observer, "mock");
+        // Start user -- this will update state of mUserController
+        mUserController.startUser(TEST_USER_ID, true);
+        Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG);
+        assertNotNull(reportMsg);
+        UserState userState = (UserState) reportMsg.obj;
+        int oldUserId = reportMsg.arg1;
+        int newUserId = reportMsg.arg2;
+        // Call dispatchUserSwitch and verify that observer was called only once
+        mInjector.handler.clearAllRecordedMessages();
+        mUserController.dispatchUserSwitch(userState, oldUserId, newUserId);
+        Mockito.verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any());
+        Set<Integer> expectedCodes = Collections.singleton(CONTINUE_USER_SWITCH_MSG);
+        Set<Integer> actualCodes = mInjector.handler.getMessageCodes();
+        assertEquals("Unexpected message sent", expectedCodes, actualCodes);
+        Message conMsg = mInjector.handler.getMessageForCode(CONTINUE_USER_SWITCH_MSG);
+        assertNotNull(conMsg);
+        userState = (UserState) conMsg.obj;
+        assertNotNull(userState);
+        assertEquals(TEST_USER_ID, userState.mHandle.getIdentifier());
+        assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state);
+        assertEquals("Unexpected old user id", 0, conMsg.arg1);
+        assertEquals("Unexpected new user id", TEST_USER_ID, conMsg.arg2);
+    }
+
+    @SmallTest
+    public void testDispatchUserSwitchBadReceiver() throws RemoteException {
+        // Prepare mock observer which doesn't notify the callback and register it
+        IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
+        when(observer.asBinder()).thenReturn(new Binder());
+        mUserController.registerUserSwitchObserver(observer, "mock");
+        // Start user -- this will update state of mUserController
+        mUserController.startUser(TEST_USER_ID, true);
+        Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG);
+        assertNotNull(reportMsg);
+        UserState userState = (UserState) reportMsg.obj;
+        int oldUserId = reportMsg.arg1;
+        int newUserId = reportMsg.arg2;
+        // Call dispatchUserSwitch and verify that observer was called only once
+        mInjector.handler.clearAllRecordedMessages();
+        mUserController.dispatchUserSwitch(userState, oldUserId, newUserId);
+        Mockito.verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any());
+        // Verify that CONTINUE_USER_SWITCH_MSG is not sent (triggers timeout)
+        Set<Integer> actualCodes = mInjector.handler.getMessageCodes();
+        assertTrue("No messages should be sent", actualCodes.isEmpty());
+    }
+
+    @SmallTest
+    public void testContinueUserSwitch() throws RemoteException {
+        // Start user -- this will update state of mUserController
+        mUserController.startUser(TEST_USER_ID, true);
+        Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG);
+        assertNotNull(reportMsg);
+        UserState userState = (UserState) reportMsg.obj;
+        int oldUserId = reportMsg.arg1;
+        int newUserId = reportMsg.arg2;
+        mInjector.handler.clearAllRecordedMessages();
+        // Verify that continueUserSwitch worked as expected
+        mUserController.continueUserSwitch(userState, oldUserId, newUserId);
+        Mockito.verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
+        continueUserSwitchAssertions();
+    }
+
+    @SmallTest
+    public void testContinueUserSwitchUIDisabled() throws RemoteException {
+        mUserController.mUserSwitchUiEnabled = false;
+        // Start user -- this will update state of mUserController
+        mUserController.startUser(TEST_USER_ID, true);
+        Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG);
+        assertNotNull(reportMsg);
+        UserState userState = (UserState) reportMsg.obj;
+        int oldUserId = reportMsg.arg1;
+        int newUserId = reportMsg.arg2;
+        mInjector.handler.clearAllRecordedMessages();
+        // Verify that continueUserSwitch worked as expected
+        mUserController.continueUserSwitch(userState, oldUserId, newUserId);
+        Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
+        continueUserSwitchAssertions();
+    }
+
+    private void continueUserSwitchAssertions() throws RemoteException {
+        Set<Integer> expectedCodes = Collections.singleton(REPORT_USER_SWITCH_COMPLETE_MSG);
+        Set<Integer> actualCodes = mInjector.handler.getMessageCodes();
+        assertEquals("Unexpected message sent", expectedCodes, actualCodes);
+        Message msg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG);
+        assertNotNull(msg);
+        assertEquals("Unexpected userId", TEST_USER_ID, msg.arg1);
+    }
+
+    @SmallTest
+    public void testDispatchUserSwitchComplete() throws RemoteException {
+        // Prepare mock observer and register it
+        IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
+        when(observer.asBinder()).thenReturn(new Binder());
+        mUserController.registerUserSwitchObserver(observer, "mock");
+        // Start user -- this will update state of mUserController
+        mUserController.startUser(TEST_USER_ID, true);
+        Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG);
+        assertNotNull(reportMsg);
+        int newUserId = reportMsg.arg2;
+        mInjector.handler.clearAllRecordedMessages();
+        // Mockito can't reset only interactions, so just verify that this hasn't been
+        // called with 'false' until after dispatchUserSwitchComplete.
+        Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(false);
+        // Call dispatchUserSwitchComplete
+        mUserController.dispatchUserSwitchComplete(newUserId);
+        Mockito.verify(observer, times(1)).onUserSwitchComplete(anyInt());
+        Mockito.verify(observer).onUserSwitchComplete(TEST_USER_ID);
+        Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(false);
+    }
+
+    private void setUpUser(int userId, int flags) {
+        UserInfo userInfo = new UserInfo(userId, "User" + userId, flags);
+        when(mInjector.userManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo);
+    }
+
+    private static List<String> getActions(List<Intent> intents) {
+        List<String> result = new ArrayList<>();
+        for (Intent intent : intents) {
+            result.add(intent.getAction());
+        }
+        return result;
+    }
+
+    // Should be public to allow mocking
+    public static class TestInjector extends UserController.Injector {
+        TestHandler handler;
+        TestHandler uiHandler;
+        HandlerThread handlerThread;
+        UserManagerService userManagerMock;
+        UserManagerInternal userManagerInternalMock;
+        WindowManagerService windowManagerMock;
+        private Context mCtx;
+        List<Intent> sentIntents = new ArrayList<>();
+
+        TestInjector(Context ctx) {
+            super(null);
+            mCtx = ctx;
+            handlerThread = new HandlerThread(TAG);
+            handlerThread.start();
+            handler = new TestHandler(handlerThread.getLooper());
+            uiHandler = new TestHandler(handlerThread.getLooper());
+            userManagerMock = mock(UserManagerService.class);
+            userManagerInternalMock = mock(UserManagerInternal.class);
+            windowManagerMock = mock(WindowManagerService.class);
+        }
+
+        @Override
+        protected Handler getHandler(Handler.Callback callback) {
+            return handler;
+        }
+
+        @Override
+        protected Handler getUiHandler(Handler.Callback callback) {
+            return uiHandler;
+        }
+
+        @Override
+        protected UserManagerService getUserManager() {
+            return userManagerMock;
+        }
+
+        @Override
+        UserManagerInternal getUserManagerInternal() {
+            return userManagerInternalMock;
+        }
+
+        @Override
+        protected Context getContext() {
+            return mCtx;
+        }
+
+        @Override
+        int checkCallingPermission(String permission) {
+            Log.i(TAG, "checkCallingPermission " + permission);
+            return PERMISSION_GRANTED;
+        }
+
+        @Override
+        WindowManagerService getWindowManager() {
+            return windowManagerMock;
+        }
+
+        @Override
+        void updateUserConfiguration() {
+            Log.i(TAG, "updateUserConfiguration");
+        }
+
+        @Override
+        protected int broadcastIntent(Intent intent, String resolvedType,
+                IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras,
+                String[] requiredPermissions, int appOp, Bundle bOptions, boolean ordered,
+                boolean sticky, int callingPid, int callingUid, int userId) {
+            Log.i(TAG, "broadcastIntentLocked " + intent);
+            sentIntents.add(intent);
+            return 0;
+        }
+
+        @Override
+        void reportGlobalUsageEventLocked(int event) {
+        }
+
+        @Override
+        void reportCurWakefulnessUsageEvent() {
+        }
+    }
+
+    private static class TestHandler extends Handler {
+        private final List<Message> mMessages = new ArrayList<>();
+
+        TestHandler(Looper looper) {
+            super(looper);
+        }
+
+        Set<Integer> getMessageCodes() {
+            Set<Integer> result = new LinkedHashSet<>();
+            for (Message msg : mMessages) {
+                result.add(msg.what);
+            }
+            return result;
+        }
+
+        Message getMessageForCode(int what) {
+            for (Message msg : mMessages) {
+                if (msg.what == what) {
+                    return msg;
+                }
+            }
+            return null;
+        }
+
+        void clearAllRecordedMessages() {
+            mMessages.clear();
+        }
+
+        @Override
+        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+            Message copy = new Message();
+            copy.copyFrom(msg);
+            mMessages.add(copy);
+            return super.sendMessageAtTime(msg, uptimeMillis);
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
new file mode 100644
index 0000000..7487d44
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2017 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.policy;
+
+import android.annotation.Nullable;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.IApplicationToken;
+import android.view.WindowManager;
+
+import com.android.server.wm.utils.WmDisplayCutout;
+
+public class FakeWindowState implements WindowManagerPolicy.WindowState {
+
+    public final Rect parentFrame = new Rect();
+    public final Rect displayFrame = new Rect();
+    public final Rect overscanFrame = new Rect();
+    public final Rect contentFrame = new Rect();
+    public final Rect visibleFrame = new Rect();
+    public final Rect decorFrame = new Rect();
+    public final Rect stableFrame = new Rect();
+    public Rect outsetFrame = new Rect();
+
+    public WmDisplayCutout displayCutout;
+
+    public WindowManager.LayoutParams attrs;
+    public int displayId;
+    public boolean isVoiceInteraction;
+    public boolean inMultiWindowMode;
+    public boolean visible = true;
+    public int surfaceLayer = 1;
+    public boolean isDimming = false;
+
+    public boolean policyVisible = true;
+
+    @Override
+    public int getOwningUid() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public String getOwningPackage() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overlayFrame,
+            Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame,
+            @Nullable Rect outsetFrame, WmDisplayCutout displayCutout,
+            boolean parentFrameWasClippedByDisplayCutout) {
+        this.parentFrame.set(parentFrame);
+        this.displayFrame.set(displayFrame);
+        this.overscanFrame.set(overlayFrame);
+        this.contentFrame.set(contentFrame);
+        this.visibleFrame.set(visibleFrame);
+        this.decorFrame.set(decorFrame);
+        this.stableFrame.set(stableFrame);
+        this.outsetFrame = outsetFrame == null ? null : new Rect(outsetFrame);
+        this.displayCutout = displayCutout;
+    }
+
+    @Override
+    public Rect getFrameLw() {
+        return parentFrame;
+    }
+
+    @Override
+    public Rect getDisplayFrameLw() {
+        return displayFrame;
+    }
+
+    @Override
+    public Rect getOverscanFrameLw() {
+        return overscanFrame;
+    }
+
+    @Override
+    public Rect getContentFrameLw() {
+        return contentFrame;
+    }
+
+    @Override
+    public Rect getVisibleFrameLw() {
+        return visibleFrame;
+    }
+
+    @Override
+    public boolean getGivenInsetsPendingLw() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public Rect getGivenContentInsetsLw() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public Rect getGivenVisibleInsetsLw() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public WindowManager.LayoutParams getAttrs() {
+        return attrs;
+    }
+
+    @Override
+    public boolean getNeedsMenuLw(WindowManagerPolicy.WindowState bottom) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public int getSystemUiVisibility() {
+        return attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility;
+    }
+
+    @Override
+    public int getSurfaceLayer() {
+        return surfaceLayer;
+    }
+
+    @Override
+    public int getBaseType() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public IApplicationToken getAppToken() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isVoiceInteraction() {
+        return isVoiceInteraction;
+    }
+
+    @Override
+    public boolean hasAppShownWindows() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isVisibleLw() {
+        return visible && policyVisible;
+    }
+
+    @Override
+    public boolean isDisplayedLw() {
+        return isVisibleLw();
+    }
+
+    @Override
+    public boolean isAnimatingLw() {
+        return false;
+    }
+
+    @Override
+    public boolean canAffectSystemUiFlags() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isGoneForLayoutLw() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isDrawnLw() {
+        return true;
+    }
+
+    @Override
+    public boolean hasDrawnLw() {
+        return true;
+    }
+
+    @Override
+    public boolean hideLw(boolean doAnimation) {
+        if (!policyVisible) {
+            return false;
+        }
+        policyVisible = false;
+        return true;
+    }
+
+    @Override
+    public boolean showLw(boolean doAnimation) {
+        if (policyVisible) {
+            return false;
+        }
+        policyVisible = true;
+        return true;
+    }
+
+    @Override
+    public boolean isAlive() {
+        return true;
+    }
+
+    @Override
+    public boolean isDefaultDisplay() {
+        return displayId == Display.DEFAULT_DISPLAY;
+    }
+
+    @Override
+    public boolean isDimming() {
+        return isDimming;
+    }
+
+    @Override
+    public int getWindowingMode() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isInMultiWindowMode() {
+        return inMultiWindowMode;
+    }
+
+    @Override
+    public int getRotationAnimationHint() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isInputMethodWindow() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public int getDisplayId() {
+        return displayId;
+    }
+
+    @Override
+    public boolean canAcquireSleepToken() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public void writeIdentifierToProto(ProtoOutputStream proto, long fieldId){
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isInputMethodTarget() {
+        return false;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerInsetsTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerInsetsTest.java
new file mode 100644
index 0000000..7e18ce7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerInsetsTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.policy;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class PhoneWindowManagerInsetsTest extends PhoneWindowManagerTestBase {
+
+    @Rule
+    public final ErrorCollector mErrorCollector = new ErrorCollector();
+
+    @Before
+    public void setUp() throws Exception {
+        addStatusBar();
+        addNavigationBar();
+    }
+
+    @Test
+    public void portrait() throws Exception {
+        DisplayInfo di = displayInfoForRotation(ROTATION_0, false /* withCutout */);
+
+        verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
+        verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
+        verifyConsistency(di);
+    }
+
+    @Test
+    public void portrait_withCutout() throws Exception {
+        DisplayInfo di = displayInfoForRotation(ROTATION_0, true /* withCutout */);
+
+        verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
+        verifyNonDecorInsets(di, 0, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT);
+        verifyConsistency(di);
+    }
+
+    @Test
+    public void landscape() throws Exception {
+        DisplayInfo di = displayInfoForRotation(ROTATION_90, false /* withCutout */);
+
+        verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        verifyNonDecorInsets(di, 0, 0, NAV_BAR_HEIGHT, 0);
+        verifyConsistency(di);
+    }
+
+    @Test
+    public void landscape_withCutout() throws Exception {
+        DisplayInfo di = displayInfoForRotation(ROTATION_90, true /* withCutout */);
+
+        verifyStableInsets(di, DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        verifyNonDecorInsets(di, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT, 0);
+        verifyConsistency(di);
+    }
+
+    @Test
+    public void seascape() throws Exception {
+        DisplayInfo di = displayInfoForRotation(ROTATION_270, false /* withCutout */);
+
+        verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0);
+        verifyNonDecorInsets(di, NAV_BAR_HEIGHT, 0, 0, 0);
+        verifyConsistency(di);
+    }
+
+    @Test
+    public void seascape_withCutout() throws Exception {
+        DisplayInfo di = displayInfoForRotation(ROTATION_270, true /* withCutout */);
+
+        verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0);
+        verifyNonDecorInsets(di, NAV_BAR_HEIGHT, 0, DISPLAY_CUTOUT_HEIGHT, 0);
+        verifyConsistency(di);
+    }
+
+    @Test
+    public void upsideDown() throws Exception {
+        DisplayInfo di = displayInfoForRotation(ROTATION_180, false /* withCutout */);
+
+        verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
+        verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
+        verifyConsistency(di);
+    }
+
+    @Test
+    public void upsideDown_withCutout() throws Exception {
+        DisplayInfo di = displayInfoForRotation(ROTATION_180, true /* withCutout */);
+
+        verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT);
+        verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT);
+        verifyConsistency(di);
+    }
+
+    private void verifyStableInsets(DisplayInfo di, int left, int top, int right, int bottom) {
+        mErrorCollector.checkThat("stableInsets", getStableInsetsLw(di), equalTo(new Rect(
+                left, top, right, bottom)));
+    }
+
+    private void verifyNonDecorInsets(DisplayInfo di, int left, int top, int right, int bottom) {
+        mErrorCollector.checkThat("nonDecorInsets", getNonDecorInsetsLw(di), equalTo(new Rect(
+                left, top, right, bottom)));
+    }
+
+    private void verifyConsistency(DisplayInfo di) {
+        verifyConsistency("configDisplay", di, getStableInsetsLw(di),
+                getConfigDisplayWidth(di), getConfigDisplayHeight(di));
+        verifyConsistency("nonDecorDisplay", di, getNonDecorInsetsLw(di),
+                getNonDecorDisplayWidth(di), getNonDecorDisplayHeight(di));
+    }
+
+    private void verifyConsistency(String what, DisplayInfo di, Rect insets, int width,
+            int height) {
+        mErrorCollector.checkThat(what + ":width", width,
+                equalTo(di.logicalWidth - insets.left - insets.right));
+        mErrorCollector.checkThat(what + ":height", height,
+                equalTo(di.logicalHeight - insets.top - insets.bottom));
+    }
+
+    private Rect getStableInsetsLw(DisplayInfo di) {
+        Rect result = new Rect();
+        mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+                di.displayCutout, result);
+        return result;
+    }
+
+    private Rect getNonDecorInsetsLw(DisplayInfo di) {
+        Rect result = new Rect();
+        mPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+                di.displayCutout, result);
+        return result;
+    }
+
+    private int getNonDecorDisplayWidth(DisplayInfo di) {
+        return mPolicy.getNonDecorDisplayWidth(di.logicalWidth, di.logicalHeight, di.rotation,
+                0 /* ui */, Display.DEFAULT_DISPLAY, di.displayCutout);
+    }
+
+    private int getNonDecorDisplayHeight(DisplayInfo di) {
+        return mPolicy.getNonDecorDisplayHeight(di.logicalWidth, di.logicalHeight, di.rotation,
+                0 /* ui */, Display.DEFAULT_DISPLAY, di.displayCutout);
+    }
+
+    private int getConfigDisplayWidth(DisplayInfo di) {
+        return mPolicy.getConfigDisplayWidth(di.logicalWidth, di.logicalHeight, di.rotation,
+                0 /* ui */, Display.DEFAULT_DISPLAY, di.displayCutout);
+    }
+
+    private int getConfigDisplayHeight(DisplayInfo di) {
+        return mPolicy.getConfigDisplayHeight(di.logicalWidth, di.logicalHeight, di.rotation,
+                0 /* ui */, Display.DEFAULT_DISPLAY, di.displayCutout);
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
new file mode 100644
index 0000000..97a716f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2017 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.policy;
+
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.DisplayCutout;
+import android.view.WindowManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase {
+
+    private FakeWindowState mAppWindow;
+
+    @Before
+    public void setUp() throws Exception {
+        mAppWindow = new FakeWindowState();
+        mAppWindow.attrs = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT,
+                TYPE_APPLICATION,
+                FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                PixelFormat.TRANSLUCENT);
+
+        addStatusBar();
+        addNavigationBar();
+    }
+
+    @Test
+    public void layoutWindowLw_appDrawsBars() throws Exception {
+        mAppWindow.attrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+        assertInsetBy(mAppWindow.displayFrame, 0, 0, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_appWontDrawBars() throws Exception {
+        mAppWindow.attrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.displayFrame, 0, NAV_BAR_HEIGHT);
+    }
+
+    @Test
+    public void layoutWindowLw_appWontDrawBars_forceStatus() throws Exception {
+        mAppWindow.attrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mAppWindow.attrs.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.displayFrame, 0, NAV_BAR_HEIGHT);
+    }
+
+    @Test
+    public void addingWindow_doesNotTamperWithSysuiFlags() {
+        mAppWindow.attrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mPolicy.addWindow(mAppWindow);
+
+        assertEquals(0, mAppWindow.attrs.systemUiVisibility);
+        assertEquals(0, mAppWindow.attrs.subtreeSystemUiVisibility);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout() {
+        addDisplayCutout();
+
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.displayFrame, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withhDisplayCutout_never() {
+        addDisplayCutout();
+
+        mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.displayFrame, STATUS_BAR_HEIGHT, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_layoutFullscreen() {
+        addDisplayCutout();
+
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+        assertInsetBy(mAppWindow.displayFrame, 0, 0, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_fullscreen() {
+        addDisplayCutout();
+
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.displayFrame, STATUS_BAR_HEIGHT, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() {
+        addDisplayCutout();
+
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
+        mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.displayFrame, 0, 0);
+    }
+
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_landscape() {
+        addDisplayCutout();
+        setRotation(ROTATION_90);
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetBy(mAppWindow.parentFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
+        assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mAppWindow.contentFrame,
+                DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0);
+        assertInsetBy(mAppWindow.displayFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_seascape() {
+        addDisplayCutout();
+        setRotation(ROTATION_270);
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetBy(mAppWindow.parentFrame, 0, 0, DISPLAY_CUTOUT_HEIGHT, 0);
+        assertInsetBy(mAppWindow.stableFrame, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0);
+        assertInsetBy(mAppWindow.contentFrame,
+                NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0);
+        assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0);
+        assertInsetBy(mAppWindow.displayFrame, 0, 0, DISPLAY_CUTOUT_HEIGHT, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_fullscreen_landscape() {
+        addDisplayCutout();
+        setRotation(ROTATION_90);
+
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetBy(mAppWindow.parentFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
+        assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mAppWindow.contentFrame,
+                DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_floatingInScreen() {
+        addDisplayCutout();
+
+        mAppWindow.attrs.flags = FLAG_LAYOUT_IN_SCREEN;
+        mAppWindow.attrs.type = TYPE_APPLICATION_OVERLAY;
+        mAppWindow.attrs.width = DISPLAY_WIDTH;
+        mAppWindow.attrs.height = DISPLAY_HEIGHT;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.displayFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_fullscreenInCutout_landscape() {
+        addDisplayCutout();
+        setRotation(ROTATION_90);
+
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+        mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetBy(mAppWindow.parentFrame, 0, 0, 0, 0);
+        assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mAppWindow.contentFrame,
+                DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0);
+    }
+
+    @Test
+    public void layoutHint_screenDecorWindow() {
+        addDisplayCutout();
+        mAppWindow.attrs.privateFlags |= PRIVATE_FLAG_IS_SCREEN_DECOR;
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+
+        final Rect frame = new Rect();
+        final Rect content = new Rect();
+        final Rect stable = new Rect();
+        final Rect outsets = new Rect();
+        final DisplayCutout.ParcelableWrapper cutout = new DisplayCutout.ParcelableWrapper();
+        mPolicy.getLayoutHintLw(mAppWindow.attrs, null /* taskBounds */, mFrames, frame, content,
+                stable, outsets, cutout);
+
+        assertThat(frame, equalTo(mFrames.mUnrestricted));
+        assertThat(content, equalTo(new Rect()));
+        assertThat(stable, equalTo(new Rect()));
+        assertThat(outsets, equalTo(new Rect()));
+        assertThat(cutout.get(), equalTo(DisplayCutout.NO_CUTOUT));
+    }
+
+    @Test
+    public void layoutHint_appWindow() {
+        // Initialize DisplayFrames
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+
+        final Rect outFrame = new Rect();
+        final Rect outContentInsets = new Rect();
+        final Rect outStableInsets = new Rect();
+        final Rect outOutsets = new Rect();
+        final DisplayCutout.ParcelableWrapper outDisplayCutout =
+                new DisplayCutout.ParcelableWrapper();
+
+        mPolicy.getLayoutHintLw(mAppWindow.attrs, null, mFrames, outFrame, outContentInsets,
+                outStableInsets, outOutsets, outDisplayCutout);
+
+        assertThat(outFrame, is(mFrames.mUnrestricted));
+        assertThat(outContentInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT)));
+        assertThat(outStableInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT)));
+        assertThat(outOutsets, is(new Rect()));
+        assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper()));
+    }
+
+    @Test
+    public void layoutHint_appWindowInTask() {
+        // Initialize DisplayFrames
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+
+        final Rect taskBounds = new Rect(100, 100, 200, 200);
+
+        final Rect outFrame = new Rect();
+        final Rect outContentInsets = new Rect();
+        final Rect outStableInsets = new Rect();
+        final Rect outOutsets = new Rect();
+        final DisplayCutout.ParcelableWrapper outDisplayCutout =
+                new DisplayCutout.ParcelableWrapper();
+
+        mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, outFrame, outContentInsets,
+                outStableInsets, outOutsets, outDisplayCutout);
+
+        assertThat(outFrame, is(taskBounds));
+        assertThat(outContentInsets, is(new Rect()));
+        assertThat(outStableInsets, is(new Rect()));
+        assertThat(outOutsets, is(new Rect()));
+        assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper()));
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTest.java
new file mode 100644
index 0000000..30665b5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.policy;
+
+import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
+
+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 android.graphics.PixelFormat;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.WindowManager;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class PhoneWindowManagerTest {
+
+    private static FakeWindowState createOpaqueFullscreen(boolean hasLightNavBar) {
+        final FakeWindowState state = new FakeWindowState();
+        state.attrs = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT,
+                TYPE_BASE_APPLICATION,
+                FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                PixelFormat.OPAQUE);
+        state.attrs.subtreeSystemUiVisibility =
+                hasLightNavBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0;
+        return state;
+    }
+
+    private static FakeWindowState createDimmingDialogWindow(boolean canBeImTarget) {
+        final FakeWindowState state = new FakeWindowState();
+        state.attrs = new WindowManager.LayoutParams(WRAP_CONTENT, WRAP_CONTENT,
+                TYPE_APPLICATION,
+                FLAG_DIM_BEHIND  | (canBeImTarget ? 0 : FLAG_ALT_FOCUSABLE_IM),
+                PixelFormat.TRANSLUCENT);
+        state.isDimming = true;
+        return state;
+    }
+
+    private static FakeWindowState createInputMethodWindow(boolean visible, boolean drawNavBar,
+            boolean hasLightNavBar) {
+        final FakeWindowState state = new FakeWindowState();
+        state.attrs = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT,
+                TYPE_INPUT_METHOD,
+                FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_IN_SCREEN
+                        | (drawNavBar ? FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS : 0),
+                PixelFormat.TRANSPARENT);
+        state.attrs.subtreeSystemUiVisibility =
+                hasLightNavBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0;
+        state.visible = visible;
+        state.policyVisible = visible;
+        return state;
+    }
+
+
+    @Test
+    public void testChooseNavigationColorWindowLw() throws Exception {
+        final FakeWindowState opaque = createOpaqueFullscreen(false);
+
+        final FakeWindowState dimmingImTarget = createDimmingDialogWindow(true);
+        final FakeWindowState dimmingNonImTarget = createDimmingDialogWindow(false);
+
+        final FakeWindowState visibleIme = createInputMethodWindow(true, true, false);
+        final FakeWindowState invisibleIme = createInputMethodWindow(false, true, false);
+        final FakeWindowState imeNonDrawNavBar = createInputMethodWindow(true, false, false);
+
+        // If everything is null, return null
+        assertNull(null, PhoneWindowManager.chooseNavigationColorWindowLw(
+                null, null, null, NAV_BAR_BOTTOM));
+
+        assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw(
+                opaque, opaque, null, NAV_BAR_BOTTOM));
+        assertEquals(dimmingImTarget, PhoneWindowManager.chooseNavigationColorWindowLw(
+                opaque, dimmingImTarget, null, NAV_BAR_BOTTOM));
+        assertEquals(dimmingNonImTarget, PhoneWindowManager.chooseNavigationColorWindowLw(
+                opaque, dimmingNonImTarget, null, NAV_BAR_BOTTOM));
+
+        assertEquals(visibleIme, PhoneWindowManager.chooseNavigationColorWindowLw(
+                null, null, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(visibleIme, PhoneWindowManager.chooseNavigationColorWindowLw(
+                null, dimmingImTarget, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(dimmingNonImTarget, PhoneWindowManager.chooseNavigationColorWindowLw(
+                null, dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(visibleIme, PhoneWindowManager.chooseNavigationColorWindowLw(
+                opaque, opaque, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(visibleIme, PhoneWindowManager.chooseNavigationColorWindowLw(
+                opaque, dimmingImTarget, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(dimmingNonImTarget, PhoneWindowManager.chooseNavigationColorWindowLw(
+                opaque, dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM));
+
+        assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw(
+                opaque, opaque, invisibleIme, NAV_BAR_BOTTOM));
+        assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw(
+                opaque, opaque, invisibleIme, NAV_BAR_BOTTOM));
+        assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw(
+                opaque, opaque, visibleIme, NAV_BAR_RIGHT));
+
+        // Only IME windows that have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS should be navigation color
+        // window.
+        assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw(
+                opaque, opaque, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+        assertEquals(dimmingImTarget, PhoneWindowManager.chooseNavigationColorWindowLw(
+                opaque, dimmingImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+        assertEquals(dimmingNonImTarget, PhoneWindowManager.chooseNavigationColorWindowLw(
+                opaque, dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+    }
+
+    @Test
+    public void testUpdateLightNavigationBarLw() throws Exception {
+        final FakeWindowState opaqueDarkNavBar = createOpaqueFullscreen(false);
+        final FakeWindowState opaqueLightNavBar = createOpaqueFullscreen(true);
+
+        final FakeWindowState dimming = createDimmingDialogWindow(false);
+
+        final FakeWindowState imeDrawDarkNavBar = createInputMethodWindow(true,true, false);
+        final FakeWindowState imeDrawLightNavBar = createInputMethodWindow(true,true, true);
+
+        assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                PhoneWindowManager.updateLightNavigationBarLw(
+                        SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, null, null,
+                        null, null));
+
+        // Opaque top fullscreen window overrides SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR flag.
+        assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw(
+                0, opaqueDarkNavBar, opaqueDarkNavBar, null, opaqueDarkNavBar));
+        assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw(
+                SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueDarkNavBar, opaqueDarkNavBar, null,
+                opaqueDarkNavBar));
+        assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                PhoneWindowManager.updateLightNavigationBarLw(0, opaqueLightNavBar,
+                        opaqueLightNavBar, null, opaqueLightNavBar));
+        assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                PhoneWindowManager.updateLightNavigationBarLw(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                        opaqueLightNavBar, opaqueLightNavBar, null, opaqueLightNavBar));
+
+        // Dimming window clears SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.
+        assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw(
+                0, opaqueDarkNavBar, dimming, null, dimming));
+        assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw(
+                0, opaqueLightNavBar, dimming, null, dimming));
+        assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw(
+                SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueDarkNavBar, dimming, null, dimming));
+        assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw(
+                SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, dimming, null, dimming));
+        assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw(
+                SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, dimming, imeDrawLightNavBar,
+                dimming));
+
+        // IME window clears SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
+        assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw(
+                SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, null, null, imeDrawDarkNavBar,
+                imeDrawDarkNavBar));
+
+        // Even if the top fullscreen has SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, IME window wins.
+        assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw(
+                SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, opaqueLightNavBar,
+                imeDrawDarkNavBar, imeDrawDarkNavBar));
+
+        // IME window should be able to use SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.
+        assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                PhoneWindowManager.updateLightNavigationBarLw(0, opaqueDarkNavBar,
+                        opaqueDarkNavBar, imeDrawLightNavBar, imeDrawLightNavBar));
+    }
+
+    @Test
+    public void testIsDockSideAllowedDockTop() throws Exception {
+        // Docked top is always allowed
+        assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_TOP, DOCKED_LEFT, NAV_BAR_BOTTOM,
+                true /* navigationBarCanMove */));
+        assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_TOP, DOCKED_LEFT, NAV_BAR_BOTTOM,
+                false /* navigationBarCanMove */));
+    }
+
+    @Test
+    public void testIsDockSideAllowedDockBottom() throws Exception {
+        // Cannot dock bottom
+        assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_BOTTOM, DOCKED_LEFT, NAV_BAR_BOTTOM,
+                true /* navigationBarCanMove */));
+    }
+
+    @Test
+    public void testIsDockSideAllowedNavigationBarMovable() throws Exception {
+        assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_LEFT, NAV_BAR_BOTTOM,
+                true /* navigationBarCanMove */));
+        assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_LEFT, NAV_BAR_LEFT,
+                true /* navigationBarCanMove */));
+        assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_LEFT, NAV_BAR_RIGHT,
+                true /* navigationBarCanMove */));
+        assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_LEFT, NAV_BAR_BOTTOM,
+                true /* navigationBarCanMove */));
+        assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_LEFT, NAV_BAR_RIGHT,
+                true /* navigationBarCanMove */));
+        assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_LEFT, NAV_BAR_LEFT,
+                true /* navigationBarCanMove */));
+    }
+
+    @Test
+    public void testIsDockSideAllowedNavigationBarNotMovable() throws Exception {
+        // Navigation bar is not movable such as tablets
+        assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_LEFT, NAV_BAR_BOTTOM,
+                false /* navigationBarCanMove */));
+        assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_TOP, NAV_BAR_BOTTOM,
+                false /* navigationBarCanMove */));
+        assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_RIGHT, NAV_BAR_BOTTOM,
+                false /* navigationBarCanMove */));
+        assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_LEFT, NAV_BAR_BOTTOM,
+                false /* navigationBarCanMove */));
+        assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_TOP, NAV_BAR_BOTTOM,
+                false /* navigationBarCanMove */));
+        assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_RIGHT, NAV_BAR_BOTTOM,
+                false /* navigationBarCanMove */));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
new file mode 100644
index 0000000..2c47a94
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2017 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.policy;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+
+import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.testing.TestableResources;
+import android.util.Pair;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+
+import com.android.server.policy.keyguard.KeyguardServiceDelegate;
+import com.android.server.wm.DisplayFrames;
+import com.android.server.wm.utils.WmDisplayCutout;
+
+import org.junit.Before;
+
+public class PhoneWindowManagerTestBase {
+    static final int DISPLAY_WIDTH = 500;
+    static final int DISPLAY_HEIGHT = 1000;
+
+    static final int STATUS_BAR_HEIGHT = 10;
+    static final int NAV_BAR_HEIGHT = 15;
+    static final int DISPLAY_CUTOUT_HEIGHT = 8;
+
+    TestablePhoneWindowManager mPolicy;
+    TestContextWrapper mContext;
+    DisplayFrames mFrames;
+
+    FakeWindowState mStatusBar;
+    FakeWindowState mNavigationBar;
+    private boolean mHasDisplayCutout;
+    private int mRotation = ROTATION_0;
+
+    @Before
+    public void setUpBase() throws Exception {
+        mContext = new TestContextWrapper(InstrumentationRegistry.getTargetContext());
+        mContext.getResourceMocker().addOverride(
+                com.android.internal.R.dimen.status_bar_height_portrait, STATUS_BAR_HEIGHT);
+        mContext.getResourceMocker().addOverride(
+                com.android.internal.R.dimen.status_bar_height_landscape, STATUS_BAR_HEIGHT);
+        mContext.getResourceMocker().addOverride(
+                com.android.internal.R.dimen.navigation_bar_height, NAV_BAR_HEIGHT);
+        mContext.getResourceMocker().addOverride(
+                com.android.internal.R.dimen.navigation_bar_height_landscape, NAV_BAR_HEIGHT);
+        mContext.getResourceMocker().addOverride(
+                com.android.internal.R.dimen.navigation_bar_width, NAV_BAR_HEIGHT);
+
+        mPolicy = TestablePhoneWindowManager.create(mContext);
+
+        updateDisplayFrames();
+    }
+
+    public void setRotation(int rotation) {
+        mRotation = rotation;
+        updateDisplayFrames();
+    }
+
+    private void updateDisplayFrames() {
+        Pair<DisplayInfo, WmDisplayCutout> info = displayInfoAndCutoutForRotation(mRotation,
+                mHasDisplayCutout);
+        mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info.first, info.second);
+    }
+
+    public void addStatusBar() {
+        mStatusBar = new FakeWindowState();
+        mStatusBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, STATUS_BAR_HEIGHT,
+                TYPE_STATUS_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT);
+        mStatusBar.attrs.gravity = Gravity.TOP;
+
+        mPolicy.addWindow(mStatusBar);
+        mPolicy.mLastSystemUiFlags |= View.STATUS_BAR_TRANSPARENT;
+    }
+
+    public void addNavigationBar() {
+        mNavigationBar = new FakeWindowState();
+        mNavigationBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, NAV_BAR_HEIGHT,
+                TYPE_NAVIGATION_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT);
+        mNavigationBar.attrs.gravity = Gravity.BOTTOM;
+
+        mPolicy.addWindow(mNavigationBar);
+        mPolicy.mHasNavigationBar = true;
+        mPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT;
+    }
+
+    public void addDisplayCutout() {
+        mHasDisplayCutout = true;
+        updateDisplayFrames();
+    }
+
+    /** Asserts that {@code actual} is inset by the given amounts from the full display rect. */
+    public void assertInsetBy(Rect actual, int expectedInsetLeft, int expectedInsetTop,
+            int expectedInsetRight, int expectedInsetBottom) {
+        assertEquals(new Rect(expectedInsetLeft, expectedInsetTop,
+                mFrames.mDisplayWidth - expectedInsetRight,
+                mFrames.mDisplayHeight - expectedInsetBottom), actual);
+    }
+
+    /**
+     * Asserts that {@code actual} is inset by the given amounts from the full display rect.
+     *
+     * Convenience wrapper for when only the top and bottom inset are non-zero.
+     */
+    public void assertInsetByTopBottom(Rect actual, int expectedInsetTop, int expectedInsetBottom) {
+        assertInsetBy(actual, 0, expectedInsetTop, 0, expectedInsetBottom);
+    }
+
+    public static DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) {
+        return displayInfoAndCutoutForRotation(rotation, withDisplayCutout).first;
+    }
+    public static Pair<DisplayInfo, WmDisplayCutout> displayInfoAndCutoutForRotation(int rotation,
+            boolean withDisplayCutout) {
+        DisplayInfo info = new DisplayInfo();
+        WmDisplayCutout cutout = null;
+
+        final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270;
+        info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
+        info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
+        info.rotation = rotation;
+        if (withDisplayCutout) {
+            cutout = WmDisplayCutout.computeSafeInsets(
+                    displayCutoutForRotation(rotation), info.logicalWidth,
+                    info.logicalHeight);
+            info.displayCutout = cutout.getDisplayCutout();
+        } else {
+            info.displayCutout = null;
+        }
+        return Pair.create(info, cutout);
+    }
+
+    private static DisplayCutout displayCutoutForRotation(int rotation) {
+        Path p = new Path();
+        p.addRect(DISPLAY_WIDTH / 4, 0, DISPLAY_WIDTH * 3 / 4, DISPLAY_CUTOUT_HEIGHT,
+                Path.Direction.CCW);
+
+        Matrix m = new Matrix();
+        transformPhysicalToLogicalCoordinates(rotation, DISPLAY_WIDTH, DISPLAY_HEIGHT, m);
+        p.transform(m);
+
+        return DisplayCutout.fromBounds(p);
+    }
+
+    static class TestContextWrapper extends ContextWrapper {
+        private final TestableResources mResourceMocker;
+
+        public TestContextWrapper(Context targetContext) {
+            super(targetContext);
+            mResourceMocker = new TestableResources(targetContext.getResources());
+        }
+
+        @Override
+        public int checkPermission(String permission, int pid, int uid) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+
+        @Override
+        public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+
+        @Override
+        public Resources getResources() {
+            return mResourceMocker.getResources();
+        }
+
+        public TestableResources getResourceMocker() {
+            return mResourceMocker;
+        }
+    }
+
+    static class TestablePhoneWindowManager extends PhoneWindowManager {
+
+        public TestablePhoneWindowManager() {
+        }
+
+        @Override
+        void initializeHdmiState() {
+            // Do nothing.
+        }
+
+        @Override
+        Context getSystemUiContext() {
+            return mContext;
+        }
+
+        void addWindow(WindowState state) {
+            if (state instanceof FakeWindowState) {
+                ((FakeWindowState) state).surfaceLayer =
+                        getWindowLayerFromTypeLw(state.getAttrs().type,
+                                true /* canAddInternalSystemWindow */);
+            }
+            adjustWindowParamsLw(state, state.getAttrs(), true /* hasStatusBarPermission */);
+            assertEquals(WindowManagerGlobal.ADD_OKAY, prepareAddWindowLw(state, state.getAttrs()));
+        }
+
+        public static TestablePhoneWindowManager create(Context context) {
+            TestablePhoneWindowManager[] policy = new TestablePhoneWindowManager[1];
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+                policy[0] = new TestablePhoneWindowManager();
+                policy[0].mContext = context;
+                policy[0].mKeyguardDelegate = mock(KeyguardServiceDelegate.class);
+                policy[0].mAccessibilityManager = new AccessibilityManager(context,
+                        mock(IAccessibilityManager.class), UserHandle.USER_CURRENT);
+                policy[0].mSystemGestures = mock(SystemGesturesPointerEventListener.class);
+                policy[0].mNavigationBarCanMove = true;
+                policy[0].mPortraitRotation = ROTATION_0;
+                policy[0].mLandscapeRotation = ROTATION_90;
+                policy[0].mUpsideDownRotation = ROTATION_180;
+                policy[0].mSeascapeRotation = ROTATION_270;
+                policy[0].onConfigurationChanged();
+            });
+            return policy[0];
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java b/services/tests/servicestests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java
new file mode 100644
index 0000000..164c80b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the {@link TaskStack} class.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:com.android.server.wm.AnimatingAppWindowTokenRegistryTest
+ */
+@SmallTest
+@Presubmit
+@FlakyTest(detail = "Promote once confirmed non-flaky")
+@RunWith(AndroidJUnit4.class)
+public class AnimatingAppWindowTokenRegistryTest extends WindowTestsBase {
+
+    @Mock
+    AnimationAdapter mAdapter;
+
+    @Mock
+    Runnable mMockEndDeferFinishCallback1;
+    @Mock
+    Runnable mMockEndDeferFinishCallback2;
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testDeferring() throws Exception {
+        final AppWindowToken window1 = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final AppWindowToken window2 = createAppWindow(window1.getTask(), ACTIVITY_TYPE_STANDARD,
+                "window2").mAppToken;
+        final AnimatingAppWindowTokenRegistry registry =
+                window1.getStack().getAnimatingAppWindowTokenRegistry();
+
+        window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */);
+        window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */);
+        assertTrue(window1.isSelfAnimating());
+        assertTrue(window2.isSelfAnimating());
+
+        // Make sure that first animation finish is deferred, second one is not deferred, and first
+        // one gets cancelled.
+        assertTrue(registry.notifyAboutToFinish(window1, mMockEndDeferFinishCallback1));
+        assertFalse(registry.notifyAboutToFinish(window2, mMockEndDeferFinishCallback2));
+        verify(mMockEndDeferFinishCallback1).run();
+        verifyZeroInteractions(mMockEndDeferFinishCallback2);
+    }
+
+    @Test
+    public void testContainerRemoved() throws Exception {
+        final AppWindowToken window1 = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final AppWindowToken window2 = createAppWindow(window1.getTask(), ACTIVITY_TYPE_STANDARD,
+                "window2").mAppToken;
+        final AnimatingAppWindowTokenRegistry registry =
+                window1.getStack().getAnimatingAppWindowTokenRegistry();
+
+        window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */);
+        window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */);
+        assertTrue(window1.isSelfAnimating());
+        assertTrue(window2.isSelfAnimating());
+
+        // Make sure that first animation finish is deferred, and removing the second window stops
+        // finishes all pending deferred finishings.
+        registry.notifyAboutToFinish(window1, mMockEndDeferFinishCallback1);
+        window2.setParent(null);
+        verify(mMockEndDeferFinishCallback1).run();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/servicestests/src/com/android/server/wm/AppTransitionTests.java
new file mode 100644
index 0000000..be7d781
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/AppTransitionTests.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 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.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link AppTransition}.
+ *
+ * atest AppTransitionTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AppTransitionTests {
+
+    @Rule
+    public final WindowManagerServiceRule mRule = new WindowManagerServiceRule();
+    private WindowManagerService mWm;
+
+    @Before
+    public void setUp() throws Exception {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        mWm = mRule.getWindowManagerService();
+    }
+
+    @Test
+    public void testKeyguardOverride() throws Exception {
+        mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */);
+        mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */);
+        assertEquals(TRANSIT_KEYGUARD_GOING_AWAY, mWm.mAppTransition.getAppTransition());
+    }
+
+    @Test
+    public void testKeyguardKeep() throws Exception {
+        mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */);
+        mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */);
+        assertEquals(TRANSIT_KEYGUARD_GOING_AWAY, mWm.mAppTransition.getAppTransition());
+    }
+
+    @Test
+    public void testForceOverride() throws Exception {
+        mWm.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE, false /* alwaysKeepCurrent */);
+        mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */,
+                0 /* flags */, true /* forceOverride */);
+        assertEquals(TRANSIT_ACTIVITY_OPEN, mWm.mAppTransition.getAppTransition());
+    }
+
+    @Test
+    public void testCrashing() throws Exception {
+        mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */);
+        mWm.prepareAppTransition(TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */);
+        assertEquals(TRANSIT_CRASHING_ACTIVITY_CLOSE, mWm.mAppTransition.getAppTransition());
+    }
+
+    @Test
+    public void testKeepKeyguard_withCrashing() throws Exception {
+        mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */);
+        mWm.prepareAppTransition(TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */);
+        assertEquals(TRANSIT_KEYGUARD_GOING_AWAY, mWm.mAppTransition.getAppTransition());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
new file mode 100644
index 0000000..e0645b1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2017 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 android.support.test.filters.FlakyTest;
+import org.junit.Test;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.EMPTY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import com.android.server.wm.WindowTestUtils.TestTaskWindowContainerController;
+
+/**
+ * Test class for {@link AppWindowContainerController}.
+ *
+ * atest FrameworksServicesTests:com.android.server.wm.AppWindowContainerControllerTests
+ */
+@SmallTest
+@Presubmit
+@FlakyTest(bugId = 74078662)
+@org.junit.runner.RunWith(AndroidJUnit4.class)
+public class AppWindowContainerControllerTests extends WindowTestsBase {
+
+    @Test
+    public void testRemoveContainer() throws Exception {
+        final WindowTestUtils.TestAppWindowContainerController controller =
+                createAppWindowController();
+
+        // Assert token was added to display.
+        assertNotNull(mDisplayContent.getWindowToken(controller.mToken.asBinder()));
+        // Assert that the container was created and linked.
+        assertNotNull(controller.mContainer);
+
+        controller.removeContainer(mDisplayContent.getDisplayId());
+
+        // Assert token was remove from display.
+        assertNull(mDisplayContent.getWindowToken(controller.mToken.asBinder()));
+        // Assert that the container was removed.
+        assertNull(controller.mContainer);
+    }
+
+    @Test
+    public void testSetOrientation() throws Exception {
+        final WindowTestUtils.TestAppWindowContainerController controller =
+                createAppWindowController();
+
+        // Assert orientation is unspecified to start.
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, controller.getOrientation());
+
+        controller.setOrientation(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getDisplayId(),
+                EMPTY /* displayConfig */, false /* freezeScreenIfNeeded */);
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, controller.getOrientation());
+
+        controller.removeContainer(mDisplayContent.getDisplayId());
+        // Assert orientation is unspecified to after container is removed.
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, controller.getOrientation());
+
+        // Reset display frozen state
+        sWm.mDisplayFrozen = false;
+    }
+
+    private void assertHasStartingWindow(AppWindowToken atoken) {
+        assertNotNull(atoken.startingSurface);
+        assertNotNull(atoken.startingData);
+        assertNotNull(atoken.startingWindow);
+    }
+
+    private void assertNoStartingWindow(AppWindowToken atoken) {
+        assertNull(atoken.startingSurface);
+        assertNull(atoken.startingWindow);
+        assertNull(atoken.startingData);
+        atoken.forAllWindows(windowState -> {
+            assertFalse(windowState.getBaseType() == TYPE_APPLICATION_STARTING);
+        }, true);
+    }
+
+    @Test
+    public void testCreateRemoveStartingWindow() throws Exception {
+        final WindowTestUtils.TestAppWindowContainerController controller =
+                createAppWindowController();
+        controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+                android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
+                false, false);
+        waitUntilHandlersIdle();
+        final AppWindowToken atoken = controller.getAppWindowToken(mDisplayContent);
+        assertHasStartingWindow(atoken);
+        controller.removeStartingWindow();
+        waitUntilHandlersIdle();
+        assertNoStartingWindow(atoken);
+    }
+
+    @Test
+    public void testAddRemoveRace() throws Exception {
+
+        // There was once a race condition between adding and removing starting windows
+        for (int i = 0; i < 1000; i++) {
+            final WindowTestUtils.TestAppWindowContainerController controller =
+                    createAppWindowController();
+            controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+                    android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
+                    false, false);
+            controller.removeStartingWindow();
+            waitUntilHandlersIdle();
+            assertNoStartingWindow(controller.getAppWindowToken(mDisplayContent));
+
+            controller.getAppWindowToken(mDisplayContent).getParent().getParent().removeImmediately();
+        }
+    }
+
+    @Test
+    public void testTransferStartingWindow() throws Exception {
+        final WindowTestUtils.TestAppWindowContainerController controller1 =
+                createAppWindowController();
+        final WindowTestUtils.TestAppWindowContainerController controller2 =
+                createAppWindowController();
+        controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+                android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
+                false, false);
+        waitUntilHandlersIdle();
+        controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+                android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
+                true, true, false, true, false, false);
+        waitUntilHandlersIdle();
+        assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent));
+        assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent));
+    }
+
+    @Test
+    public void testTransferStartingWindowWhileCreating() throws Exception {
+        final WindowTestUtils.TestAppWindowContainerController controller1 =
+                createAppWindowController();
+        final WindowTestUtils.TestAppWindowContainerController controller2 =
+                createAppWindowController();
+        ((TestWindowManagerPolicy) sWm.mPolicy).setRunnableWhenAddingSplashScreen(() -> {
+
+            // Surprise, ...! Transfer window in the middle of the creation flow.
+            controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+                    android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
+                    true, true, false, true, false, false);
+        });
+        controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+                android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
+                false, false);
+        waitUntilHandlersIdle();
+        assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent));
+        assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent));
+    }
+
+    @Test
+    public void testTryTransferStartingWindowFromHiddenAboveToken() throws Exception {
+
+        // Add two tasks on top of each other.
+        TestTaskWindowContainerController taskController =
+                new WindowTestUtils.TestTaskWindowContainerController(this);
+        final WindowTestUtils.TestAppWindowContainerController controllerTop =
+                createAppWindowController(taskController);
+        final WindowTestUtils.TestAppWindowContainerController controllerBottom =
+                createAppWindowController(taskController);
+
+        // Add a starting window.
+        controllerTop.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+                android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
+                false, false);
+        waitUntilHandlersIdle();
+
+        // Make the top one invisible, and try transfering the starting window from the top to the
+        // bottom one.
+        controllerTop.setVisibility(false, false);
+        controllerBottom.mContainer.transferStartingWindowFromHiddenAboveTokenIfNeeded();
+
+        // Assert that the bottom window now has the starting window.
+        assertNoStartingWindow(controllerTop.getAppWindowToken(mDisplayContent));
+        assertHasStartingWindow(controllerBottom.getAppWindowToken(mDisplayContent));
+    }
+
+    @Test
+    public void testReparent() throws Exception {
+        final StackWindowController stackController =
+            createStackControllerOnDisplay(mDisplayContent);
+        final WindowTestUtils.TestTaskWindowContainerController taskController1 =
+                new WindowTestUtils.TestTaskWindowContainerController(stackController);
+        final WindowTestUtils.TestAppWindowContainerController appWindowController1 =
+                createAppWindowController(taskController1);
+        final WindowTestUtils.TestTaskWindowContainerController taskController2 =
+                new WindowTestUtils.TestTaskWindowContainerController(stackController);
+        final WindowTestUtils.TestAppWindowContainerController appWindowController2 =
+                createAppWindowController(taskController2);
+        final WindowTestUtils.TestTaskWindowContainerController taskController3 =
+                new WindowTestUtils.TestTaskWindowContainerController(stackController);
+
+        try {
+            appWindowController1.reparent(taskController1, 0);
+            fail("Should not be able to reparent to the same parent");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            taskController3.setContainer(null);
+            appWindowController1.reparent(taskController3, 0);
+            fail("Should not be able to reparent to a task that doesn't have a container");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        // Reparent the app window and ensure that it is moved
+        appWindowController1.reparent(taskController2, 0);
+        assertEquals(taskController2.mContainer, appWindowController1.mContainer.getParent());
+        assertEquals(0, ((WindowTestUtils.TestAppWindowToken) appWindowController1.mContainer)
+                .positionInParent());
+        assertEquals(1, ((WindowTestUtils.TestAppWindowToken) appWindowController2.mContainer)
+                .positionInParent());
+    }
+
+    private WindowTestUtils.TestAppWindowContainerController createAppWindowController() {
+        return createAppWindowController(
+                new WindowTestUtils.TestTaskWindowContainerController(this));
+    }
+
+    private WindowTestUtils.TestAppWindowContainerController createAppWindowController(
+            WindowTestUtils.TestTaskWindowContainerController taskController) {
+        return new WindowTestUtils.TestAppWindowContainerController(taskController);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
new file mode 100644
index 0000000..f6599dc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2016 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 org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_UNSET;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+/**
+ * Tests for the {@link AppWindowToken} class.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:com.android.server.wm.AppWindowTokenTests
+ */
+@SmallTest
+// TODO: b/68267650
+// @Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AppWindowTokenTests extends WindowTestsBase {
+
+    TaskStack mStack;
+    Task mTask;
+    WindowTestUtils.TestAppWindowToken mToken;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mStack = createTaskStackOnDisplay(mDisplayContent);
+        mTask = createTaskInStack(mStack, 0 /* userId */);
+        mToken = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+
+        mTask.addChild(mToken, 0);
+    }
+
+    @Test
+    @Presubmit
+    public void testAddWindow_Order() throws Exception {
+        assertEquals(0, mToken.getWindowsCount());
+
+        final WindowState win1 = createWindow(null, TYPE_APPLICATION, mToken, "win1");
+        final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, mToken,
+                "startingWin");
+        final WindowState baseWin = createWindow(null, TYPE_BASE_APPLICATION, mToken, "baseWin");
+        final WindowState win4 = createWindow(null, TYPE_APPLICATION, mToken, "win4");
+
+        // Should not contain the windows that were added above.
+        assertEquals(4, mToken.getWindowsCount());
+        assertTrue(mToken.hasWindow(win1));
+        assertTrue(mToken.hasWindow(startingWin));
+        assertTrue(mToken.hasWindow(baseWin));
+        assertTrue(mToken.hasWindow(win4));
+
+        // The starting window should be on-top of all other windows.
+        assertEquals(startingWin, mToken.getLastChild());
+
+        // The base application window should be below all other windows.
+        assertEquals(baseWin, mToken.getFirstChild());
+        mToken.removeImmediately();
+    }
+
+    @Test
+    @Presubmit
+    public void testFindMainWindow() throws Exception {
+        assertNull(mToken.findMainWindow());
+
+        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mToken, "window1");
+        final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, mToken, "window11");
+        final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, mToken, "window12");
+        assertEquals(window1, mToken.findMainWindow());
+        window1.mAnimatingExit = true;
+        assertEquals(window1, mToken.findMainWindow());
+        final WindowState window2 = createWindow(null, TYPE_APPLICATION_STARTING, mToken, "window2");
+        assertEquals(window2, mToken.findMainWindow());
+        mToken.removeImmediately();
+    }
+
+    @Test
+    @Presubmit
+    public void testGetTopFullscreenWindow() throws Exception {
+        assertNull(mToken.getTopFullscreenWindow());
+
+        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mToken, "window1");
+        final WindowState window11 = createWindow(null, TYPE_APPLICATION, mToken, "window11");
+        final WindowState window12 = createWindow(null, TYPE_APPLICATION, mToken, "window12");
+        assertEquals(window12, mToken.getTopFullscreenWindow());
+        window12.mAttrs.width = 500;
+        assertEquals(window11, mToken.getTopFullscreenWindow());
+        window11.mAttrs.width = 500;
+        assertEquals(window1, mToken.getTopFullscreenWindow());
+        mToken.removeImmediately();
+    }
+
+    @Test
+    public void testLandscapeSeascapeRotationByApp() throws Exception {
+        // Some plumbing to get the service ready for rotation updates.
+        sWm.mDisplayReady = true;
+        sWm.mDisplayEnabled = true;
+
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
+                TYPE_BASE_APPLICATION);
+        attrs.setTitle("AppWindow");
+        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken);
+        mToken.addWindow(appWindow);
+
+        // Set initial orientation and update.
+        mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        sWm.updateOrientationFromAppTokens(mDisplayContent.getOverrideConfiguration(), null,
+                mDisplayContent.getDisplayId());
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation());
+        appWindow.resizeReported = false;
+
+        // Update the orientation to perform 180 degree rotation and check that resize was reported.
+        mToken.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+        sWm.updateOrientationFromAppTokens(mDisplayContent.getOverrideConfiguration(), null,
+                mDisplayContent.getDisplayId());
+        sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
+        assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, mDisplayContent.getLastOrientation());
+        assertTrue(appWindow.resizeReported);
+        appWindow.removeImmediately();
+    }
+
+    @Test
+    public void testLandscapeSeascapeRotationByPolicy() throws Exception {
+        // Some plumbing to get the service ready for rotation updates.
+        sWm.mDisplayReady = true;
+        sWm.mDisplayEnabled = true;
+
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
+                TYPE_BASE_APPLICATION);
+        attrs.setTitle("AppWindow");
+        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken);
+        mToken.addWindow(appWindow);
+
+        // Set initial orientation and update.
+        performRotation(Surface.ROTATION_90);
+        appWindow.resizeReported = false;
+
+        // Update the rotation to perform 180 degree rotation and check that resize was reported.
+        performRotation(Surface.ROTATION_270);
+        assertTrue(appWindow.resizeReported);
+        appWindow.removeImmediately();
+    }
+
+    private void performRotation(int rotationToReport) {
+        ((TestWindowManagerPolicy) sWm.mPolicy).rotationToReport = rotationToReport;
+        sWm.updateRotation(false, false);
+        // Simulate animator finishing orientation change
+        sWm.mRoot.mOrientationChangeComplete = true;
+        sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
+    }
+
+    @Test
+    @Presubmit
+    public void testGetOrientation() throws Exception {
+        mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+        mToken.setFillsParent(false);
+        // Can specify orientation if app doesn't fill parent.
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation());
+
+        mToken.setFillsParent(true);
+        mToken.setHidden(true);
+        mToken.sendingToBottom = true;
+        // Can not specify orientation if app isn't visible even though it fills parent.
+        assertEquals(SCREEN_ORIENTATION_UNSET, mToken.getOrientation());
+        // Can specify orientation if the current orientation candidate is orientation behind.
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation(SCREEN_ORIENTATION_BEHIND));
+    }
+
+    @Test
+    @Presubmit
+    public void testKeyguardFlagsDuringRelaunch() throws Exception {
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
+                TYPE_BASE_APPLICATION);
+        attrs.flags |= FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD;
+        attrs.setTitle("AppWindow");
+        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken);
+
+        // Add window with show when locked flag
+        mToken.addWindow(appWindow);
+        assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow());
+
+        // Start relaunching
+        mToken.startRelaunching();
+        assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow());
+
+        // Remove window and make sure that we still report back flag
+        mToken.removeChild(appWindow);
+        assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow());
+
+        // Finish relaunching and ensure flag is now not reported
+        mToken.finishRelaunching();
+        assertFalse(mToken.containsShowWhenLockedWindow() || mToken.containsDismissKeyguardWindow());
+    }
+
+    @Test
+    @FlakyTest(detail = "Promote once confirmed non-flaky")
+    public void testStuckExitingWindow() throws Exception {
+        final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
+                "closingWindow");
+        closingWindow.mAnimatingExit = true;
+        closingWindow.mRemoveOnExit = true;
+        closingWindow.mAppToken.setVisibility(null, false /* visible */, TRANSIT_UNSET,
+                true /* performLayout */, false /* isVoiceInteraction */);
+
+        // We pretended that we were running an exit animation, but that should have been cleared up
+        // by changing visibility of AppWindowToken
+        closingWindow.removeIfPossible();
+        assertTrue(closingWindow.mRemoved);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
new file mode 100644
index 0000000..ff631e7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 2017 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 com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS;
+import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END;
+import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START;
+import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.wm.WindowManagerInternal.AppTransitionListener;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+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.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.server.wm.BoundsAnimationController.BoundsAnimator;
+
+/**
+ * Test class for {@link BoundsAnimationController} to ensure that it sends the right callbacks
+ * depending on the various interactions.
+ *
+ * We are really concerned about only three of the transition states [F = fullscreen, !F = floating]
+ * F->!F, !F->!F, and !F->F. Each animation can only be cancelled from the target mid-transition,
+ * or if a new animation starts on the same target.  The tests below verifies that the target is
+ * notified of all the cases where it is animating and cancelled so that it can respond
+ * appropriately.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.BoundsAnimationControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BoundsAnimationControllerTests extends WindowTestsBase {
+
+    /**
+     * Mock value animator to simulate updates with.
+     */
+    private class MockValueAnimator extends ValueAnimator {
+
+        private float mFraction;
+
+        public MockValueAnimator getWithValue(float fraction) {
+            mFraction = fraction;
+            return this;
+        }
+
+        @Override
+        public Object getAnimatedValue() {
+            return mFraction;
+        }
+    }
+
+    /**
+     * Mock app transition to fire notifications to the bounds animator.
+     */
+    private class MockAppTransition extends AppTransition {
+
+        private AppTransitionListener mListener;
+
+        MockAppTransition(Context context) {
+            super(context, null);
+        }
+
+        @Override
+        void registerListenerLocked(AppTransitionListener listener) {
+            mListener = listener;
+        }
+
+        public void notifyTransitionPending() {
+            mListener.onAppTransitionPendingLocked();
+        }
+
+        public void notifyTransitionCancelled(int transit) {
+            mListener.onAppTransitionCancelledLocked(transit);
+        }
+
+        public void notifyTransitionStarting(int transit) {
+            mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0);
+        }
+
+        public void notifyTransitionFinished() {
+            mListener.onAppTransitionFinishedLocked(null);
+        }
+    }
+
+    /**
+     * A test animate bounds user to track callbacks from the bounds animation.
+     */
+    private class TestBoundsAnimationTarget implements BoundsAnimationTarget {
+
+        boolean mAwaitingAnimationStart;
+        boolean mMovedToFullscreen;
+        boolean mAnimationStarted;
+        boolean mSchedulePipModeChangedOnStart;
+        boolean mForcePipModeChangedCallback;
+        boolean mAnimationEnded;
+        Rect mAnimationEndFinalStackBounds;
+        boolean mSchedulePipModeChangedOnEnd;
+        boolean mBoundsUpdated;
+        boolean mCancelRequested;
+        Rect mStackBounds;
+        Rect mTaskBounds;
+
+        void initialize(Rect from) {
+            mAwaitingAnimationStart = true;
+            mMovedToFullscreen = false;
+            mAnimationStarted = false;
+            mAnimationEnded = false;
+            mAnimationEndFinalStackBounds = null;
+            mForcePipModeChangedCallback = false;
+            mSchedulePipModeChangedOnStart = false;
+            mSchedulePipModeChangedOnEnd = false;
+            mStackBounds = from;
+            mTaskBounds = null;
+            mBoundsUpdated = false;
+        }
+
+        @Override
+        public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) {
+            mAwaitingAnimationStart = false;
+            mAnimationStarted = true;
+            mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback;
+            mForcePipModeChangedCallback = forceUpdate;
+        }
+
+        @Override
+        public boolean shouldDeferStartOnMoveToFullscreen() {
+            return true;
+        }
+
+        @Override
+        public boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds) {
+            // TODO: Once we break the runs apart, we should fail() here if this is called outside
+            //       of onAnimationStart() and onAnimationEnd()
+            if (mCancelRequested) {
+                mCancelRequested = false;
+                return false;
+            } else {
+                mBoundsUpdated = true;
+                mStackBounds = stackBounds;
+                mTaskBounds = taskBounds;
+                return true;
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackBounds,
+                boolean moveToFullscreen) {
+            mAnimationEnded = true;
+            mAnimationEndFinalStackBounds = finalStackBounds;
+            mSchedulePipModeChangedOnEnd = schedulePipModeChangedCallback;
+            mMovedToFullscreen = moveToFullscreen;
+            mTaskBounds = null;
+        }
+    }
+
+    /**
+     * Drives the animations, makes common assertions along the way.
+     */
+    private class BoundsAnimationDriver {
+
+        private BoundsAnimationController mController;
+        private TestBoundsAnimationTarget mTarget;
+        private BoundsAnimator mAnimator;
+
+        private Rect mFrom;
+        private Rect mTo;
+        private Rect mLargerBounds;
+        private Rect mExpectedFinalBounds;
+
+        BoundsAnimationDriver(BoundsAnimationController controller,
+                TestBoundsAnimationTarget target) {
+            mController = controller;
+            mTarget = target;
+        }
+
+        BoundsAnimationDriver start(Rect from, Rect to) {
+            if (mAnimator != null) {
+                throw new IllegalArgumentException("Call restart() to restart an animation");
+            }
+
+            boolean fromFullscreen = from.equals(BOUNDS_FULL);
+            boolean toFullscreen = to.equals(BOUNDS_FULL);
+
+            mTarget.initialize(from);
+
+            // Started, not running
+            assertTrue(mTarget.mAwaitingAnimationStart);
+            assertTrue(!mTarget.mAnimationStarted);
+
+            startImpl(from, to);
+
+            // Ensure that the animator is paused for the all windows drawn signal when animating
+            // to/from fullscreen
+            if (fromFullscreen || toFullscreen) {
+                assertTrue(mAnimator.isPaused());
+                mController.onAllWindowsDrawn();
+            } else {
+                assertTrue(!mAnimator.isPaused());
+            }
+
+            // Started and running
+            assertTrue(!mTarget.mAwaitingAnimationStart);
+            assertTrue(mTarget.mAnimationStarted);
+
+            return this;
+        }
+
+        BoundsAnimationDriver restart(Rect to, boolean expectStartedAndPipModeChangedCallback) {
+            if (mAnimator == null) {
+                throw new IllegalArgumentException("Call start() to start a new animation");
+            }
+
+            BoundsAnimator oldAnimator = mAnimator;
+            boolean toSameBounds = mAnimator.isStarted() && to.equals(mTo);
+
+            // Reset the animation start state
+            mTarget.mAnimationStarted = false;
+
+            // Start animation
+            startImpl(mTarget.mStackBounds, to);
+
+            if (toSameBounds) {
+                // Same animator if same final bounds
+                assertSame(oldAnimator, mAnimator);
+            }
+
+            if (expectStartedAndPipModeChangedCallback) {
+                // Replacing animation with pending pip mode changed callback, ensure we update
+                assertTrue(mTarget.mAnimationStarted);
+                assertTrue(mTarget.mSchedulePipModeChangedOnStart);
+                assertTrue(mTarget.mForcePipModeChangedCallback);
+            } else {
+                // No animation start for replacing animation
+                assertTrue(!mTarget.mAnimationStarted);
+            }
+            mTarget.mAnimationStarted = true;
+            return this;
+        }
+
+        private BoundsAnimationDriver startImpl(Rect from, Rect to) {
+            boolean fromFullscreen = from.equals(BOUNDS_FULL);
+            boolean toFullscreen = to.equals(BOUNDS_FULL);
+            mFrom = new Rect(from);
+            mTo = new Rect(to);
+            mExpectedFinalBounds = new Rect(to);
+            mLargerBounds = getLargerBounds(mFrom, mTo);
+
+            // Start animation
+            final @SchedulePipModeChangedState int schedulePipModeChangedState = toFullscreen
+                    ? SCHEDULE_PIP_MODE_CHANGED_ON_START
+                    : fromFullscreen
+                            ? SCHEDULE_PIP_MODE_CHANGED_ON_END
+                            : NO_PIP_MODE_CHANGED_CALLBACKS;
+            mAnimator = mController.animateBoundsImpl(mTarget, from, to, DURATION,
+                    schedulePipModeChangedState, fromFullscreen, toFullscreen);
+
+            // Original stack bounds, frozen task bounds
+            assertEquals(mFrom, mTarget.mStackBounds);
+            assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds);
+
+            // Animating to larger size
+            if (mFrom.equals(mLargerBounds)) {
+                assertTrue(!mAnimator.animatingToLargerSize());
+            } else if (mTo.equals(mLargerBounds)) {
+                assertTrue(mAnimator.animatingToLargerSize());
+            }
+
+            return this;
+        }
+
+        BoundsAnimationDriver expectStarted(boolean schedulePipModeChanged) {
+            // Callback made
+            assertTrue(mTarget.mAnimationStarted);
+
+            assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnStart);
+            return this;
+        }
+
+        BoundsAnimationDriver update(float t) {
+            mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(t));
+
+            // Temporary stack bounds, frozen task bounds
+            if (t == 0f) {
+                assertEquals(mFrom, mTarget.mStackBounds);
+            } else if (t == 1f) {
+                assertEquals(mTo, mTarget.mStackBounds);
+            } else {
+                assertNotEquals(mFrom, mTarget.mStackBounds);
+                assertNotEquals(mTo, mTarget.mStackBounds);
+            }
+            assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds);
+            return this;
+        }
+
+        BoundsAnimationDriver cancel() {
+            // Cancel
+            mTarget.mCancelRequested = true;
+            mTarget.mBoundsUpdated = false;
+            mExpectedFinalBounds = null;
+
+            // Update
+            mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(0.5f));
+
+            // Not started, not running, cancel reset
+            assertTrue(!mTarget.mCancelRequested);
+
+            // Stack/task bounds not updated
+            assertTrue(!mTarget.mBoundsUpdated);
+
+            // Callback made
+            assertTrue(mTarget.mAnimationEnded);
+            assertNull(mTarget.mAnimationEndFinalStackBounds);
+
+            return this;
+        }
+
+        BoundsAnimationDriver end() {
+            mAnimator.end();
+
+            // Final stack bounds
+            assertEquals(mTo, mTarget.mStackBounds);
+            assertEquals(mExpectedFinalBounds, mTarget.mAnimationEndFinalStackBounds);
+            assertNull(mTarget.mTaskBounds);
+
+            return this;
+        }
+
+        BoundsAnimationDriver expectEnded(boolean schedulePipModeChanged,
+                boolean moveToFullscreen) {
+            // Callback made
+            assertTrue(mTarget.mAnimationEnded);
+
+            assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnEnd);
+            assertEquals(moveToFullscreen, mTarget.mMovedToFullscreen);
+            return this;
+        }
+
+        private Rect getLargerBounds(Rect r1, Rect r2) {
+            int r1Area = r1.width() * r1.height();
+            int r2Area = r2.width() * r2.height();
+            if (r1Area <= r2Area) {
+                return r2;
+            } else {
+                return r1;
+            }
+        }
+    }
+
+    // Constants
+    private static final boolean SCHEDULE_PIP_MODE_CHANGED = true;
+    private static final boolean MOVE_TO_FULLSCREEN = true;
+    private static final int DURATION = 100;
+
+    // Some dummy bounds to represent fullscreen and floating bounds
+    private static final Rect BOUNDS_FULL = new Rect(0, 0, 100, 100);
+    private static final Rect BOUNDS_FLOATING = new Rect(60, 60, 95, 95);
+    private static final Rect BOUNDS_SMALLER_FLOATING = new Rect(80, 80, 95, 95);
+
+    // Common
+    private MockAppTransition mMockAppTransition;
+    private MockValueAnimator mMockAnimator;
+    private TestBoundsAnimationTarget mTarget;
+    private BoundsAnimationController mController;
+    private BoundsAnimationDriver mDriver;
+
+    // Temp
+    private Rect mTmpRect = new Rect();
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final Handler handler = new Handler(Looper.getMainLooper());
+        mMockAppTransition = new MockAppTransition(context);
+        mMockAnimator = new MockValueAnimator();
+        mTarget = new TestBoundsAnimationTarget();
+        mController = new BoundsAnimationController(context, mMockAppTransition, handler, null);
+        mDriver = new BoundsAnimationDriver(mController, mTarget);
+    }
+
+    /** BASE TRANSITIONS **/
+
+    @UiThreadTest
+    @Test
+    public void testFullscreenToFloatingTransition() throws Exception {
+        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+                .update(0f)
+                .update(0.5f)
+                .update(1f)
+                .end()
+                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFloatingToFullscreenTransition() throws Exception {
+        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
+                .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
+                .update(0f)
+                .update(0.5f)
+                .update(1f)
+                .end()
+                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFloatingToSmallerFloatingTransition() throws Exception {
+        mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING)
+                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+                .update(0f)
+                .update(0.5f)
+                .update(1f)
+                .end()
+                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFloatingToLargerFloatingTransition() throws Exception {
+        mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING)
+                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+                .update(0f)
+                .update(0.5f)
+                .update(1f)
+                .end()
+                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+    }
+
+    /** F->!F w/ CANCEL **/
+
+    @UiThreadTest
+    @Test
+    public void testFullscreenToFloatingCancelFromTarget() throws Exception {
+        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+                .update(0.25f)
+                .cancel()
+                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFullscreenToFloatingCancelFromAnimationToSameBounds() throws Exception {
+        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+                .update(0.25f)
+                .restart(BOUNDS_FLOATING, false /* expectStartedAndPipModeChangedCallback */)
+                .end()
+                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFullscreenToFloatingCancelFromAnimationToFloatingBounds() throws Exception {
+        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+                .update(0.25f)
+                .restart(BOUNDS_SMALLER_FLOATING,
+                        false /* expectStartedAndPipModeChangedCallback */)
+                .end()
+                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception {
+        // When animating from fullscreen and the animation is interruped, we expect the animation
+        // start callback to be made, with a forced pip mode change callback
+        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+                .update(0.25f)
+                .restart(BOUNDS_FULL, true /* expectStartedAndPipModeChangedCallback */)
+                .end()
+                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
+    }
+
+    /** !F->F w/ CANCEL **/
+
+    @UiThreadTest
+    @Test
+    public void testFloatingToFullscreenCancelFromTarget() throws Exception {
+        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
+                .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
+                .update(0.25f)
+                .cancel()
+                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFloatingToFullscreenCancelFromAnimationToSameBounds() throws Exception {
+        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
+                .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
+                .update(0.25f)
+                .restart(BOUNDS_FULL, false /* expectStartedAndPipModeChangedCallback */)
+                .end()
+                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFloatingToFullscreenCancelFromAnimationToFloatingBounds() throws Exception {
+        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
+                .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
+                .update(0.25f)
+                .restart(BOUNDS_SMALLER_FLOATING,
+                        false /* expectStartedAndPipModeChangedCallback */)
+                .end()
+                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
+    }
+
+    /** !F->!F w/ CANCEL **/
+
+    @UiThreadTest
+    @Test
+    public void testFloatingToSmallerFloatingCancelFromTarget() throws Exception {
+        mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING)
+                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+                .update(0.25f)
+                .cancel()
+                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFloatingToLargerFloatingCancelFromTarget() throws Exception {
+        mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING)
+                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+                .update(0.25f)
+                .cancel()
+                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+    }
+
+    /** MISC **/
+
+    @UiThreadTest
+    @Test
+    public void testBoundsAreCopied() throws Exception {
+        Rect from = new Rect(0, 0, 100, 100);
+        Rect to = new Rect(25, 25, 75, 75);
+        mDriver.start(from, to)
+                .update(0.25f)
+                .end();
+        assertEquals(new Rect(0, 0, 100, 100), from);
+        assertEquals(new Rect(25, 25, 75, 75), to);
+    }
+
+    /**
+     * @return whether the task and stack bounds would be the same if they were at the same offset.
+     */
+    private boolean assertEqualSizeAtOffset(Rect stackBounds, Rect taskBounds) {
+        mTmpRect.set(taskBounds);
+        mTmpRect.offsetTo(stackBounds.left, stackBounds.top);
+        return stackBounds.equals(mTmpRect);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/ConfigurationContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/ConfigurationContainerTests.java
new file mode 100644
index 0000000..192e156
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/ConfigurationContainerTests.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2017 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.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+import static android.content.res.Configuration.EMPTY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Configuration;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test class for {@link ConfigurationContainer}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.ConfigurationContainerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ConfigurationContainerTests {
+
+    @Test
+    public void testConfigurationInit() throws Exception {
+        // Check root container initial config.
+        final TestConfigurationContainer root = new TestConfigurationContainer();
+        assertEquals(EMPTY, root.getOverrideConfiguration());
+        assertEquals(EMPTY, root.getMergedOverrideConfiguration());
+        assertEquals(EMPTY, root.getConfiguration());
+
+        // Check child initial config.
+        final TestConfigurationContainer child1 = root.addChild();
+        assertEquals(EMPTY, child1.getOverrideConfiguration());
+        assertEquals(EMPTY, child1.getMergedOverrideConfiguration());
+        assertEquals(EMPTY, child1.getConfiguration());
+
+        // Check child initial config if root has overrides.
+        final Configuration rootOverrideConfig = new Configuration();
+        rootOverrideConfig.fontScale = 1.3f;
+        root.onOverrideConfigurationChanged(rootOverrideConfig);
+        final TestConfigurationContainer child2 = root.addChild();
+        assertEquals(EMPTY, child2.getOverrideConfiguration());
+        assertEquals(rootOverrideConfig, child2.getMergedOverrideConfiguration());
+        assertEquals(rootOverrideConfig, child2.getConfiguration());
+
+        // Check child initial config if root has parent config set.
+        final Configuration rootParentConfig = new Configuration();
+        rootParentConfig.fontScale = 0.8f;
+        rootParentConfig.orientation = SCREEN_ORIENTATION_LANDSCAPE;
+        root.onConfigurationChanged(rootParentConfig);
+        final Configuration rootFullConfig = new Configuration(rootParentConfig);
+        rootFullConfig.updateFrom(rootOverrideConfig);
+
+        final TestConfigurationContainer child3 = root.addChild();
+        assertEquals(EMPTY, child3.getOverrideConfiguration());
+        assertEquals(rootOverrideConfig, child3.getMergedOverrideConfiguration());
+        assertEquals(rootFullConfig, child3.getConfiguration());
+    }
+
+    @Test
+    public void testConfigurationChangeOnAddRemove() throws Exception {
+        // Init root's config.
+        final TestConfigurationContainer root = new TestConfigurationContainer();
+        final Configuration rootOverrideConfig = new Configuration();
+        rootOverrideConfig.fontScale = 1.3f;
+        root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+        // Init child's config.
+        final TestConfigurationContainer child = root.addChild();
+        final Configuration childOverrideConfig = new Configuration();
+        childOverrideConfig.densityDpi = 320;
+        child.onOverrideConfigurationChanged(childOverrideConfig);
+        final Configuration mergedOverrideConfig = new Configuration(root.getConfiguration());
+        mergedOverrideConfig.updateFrom(childOverrideConfig);
+
+        // Check configuration update when child is removed from parent.
+        root.removeChild(child);
+        assertEquals(childOverrideConfig, child.getOverrideConfiguration());
+        assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration());
+        assertEquals(mergedOverrideConfig, child.getConfiguration());
+
+        // It may be paranoia... but let's check if parent's config didn't change after removal.
+        assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+        assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+        assertEquals(rootOverrideConfig, root.getConfiguration());
+
+        // Init different root
+        final TestConfigurationContainer root2 = new TestConfigurationContainer();
+        final Configuration rootOverrideConfig2 = new Configuration();
+        rootOverrideConfig2.fontScale = 1.1f;
+        root2.onOverrideConfigurationChanged(rootOverrideConfig2);
+
+        // Check configuration update when child is added to different parent.
+        mergedOverrideConfig.setTo(rootOverrideConfig2);
+        mergedOverrideConfig.updateFrom(childOverrideConfig);
+        root2.addChild(child);
+        assertEquals(childOverrideConfig, child.getOverrideConfiguration());
+        assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration());
+        assertEquals(mergedOverrideConfig, child.getConfiguration());
+    }
+
+    @Test
+    public void testConfigurationChangePropagation() throws Exception {
+        // Builds 3-level vertical hierarchy with one configuration container on each level.
+        // In addition to different overrides on each level, everyone in hierarchy will have one
+        // common overridden value - orientation;
+
+        // Init root's config.
+        final TestConfigurationContainer root = new TestConfigurationContainer();
+        final Configuration rootOverrideConfig = new Configuration();
+        rootOverrideConfig.fontScale = 1.3f;
+        rootOverrideConfig.orientation = SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+        root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+        // Init children.
+        final TestConfigurationContainer child1 = root.addChild();
+        final Configuration childOverrideConfig1 = new Configuration();
+        childOverrideConfig1.densityDpi = 320;
+        childOverrideConfig1.orientation = SCREEN_ORIENTATION_LANDSCAPE;
+        child1.onOverrideConfigurationChanged(childOverrideConfig1);
+
+        final TestConfigurationContainer child2 = child1.addChild();
+        final Configuration childOverrideConfig2 = new Configuration();
+        childOverrideConfig2.screenWidthDp = 150;
+        childOverrideConfig2.orientation = SCREEN_ORIENTATION_PORTRAIT;
+        child2.onOverrideConfigurationChanged(childOverrideConfig2);
+
+        // Check configuration on all levels when root override is updated.
+        rootOverrideConfig.smallestScreenWidthDp = 200;
+        root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+        final Configuration mergedOverrideConfig1 = new Configuration(rootOverrideConfig);
+        mergedOverrideConfig1.updateFrom(childOverrideConfig1);
+        final Configuration mergedConfig1 = new Configuration(mergedOverrideConfig1);
+
+        final Configuration mergedOverrideConfig2 = new Configuration(mergedOverrideConfig1);
+        mergedOverrideConfig2.updateFrom(childOverrideConfig2);
+        final Configuration mergedConfig2 = new Configuration(mergedOverrideConfig2);
+
+        assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+        assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+        assertEquals(rootOverrideConfig, root.getConfiguration());
+
+        assertEquals(childOverrideConfig1, child1.getOverrideConfiguration());
+        assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration());
+        assertEquals(mergedConfig1, child1.getConfiguration());
+
+        assertEquals(childOverrideConfig2, child2.getOverrideConfiguration());
+        assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration());
+        assertEquals(mergedConfig2, child2.getConfiguration());
+
+        // Check configuration on all levels when root parent config is updated.
+        final Configuration rootParentConfig = new Configuration();
+        rootParentConfig.screenHeightDp = 100;
+        rootParentConfig.orientation = SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+        root.onConfigurationChanged(rootParentConfig);
+        final Configuration mergedRootConfig = new Configuration(rootParentConfig);
+        mergedRootConfig.updateFrom(rootOverrideConfig);
+
+        mergedConfig1.setTo(mergedRootConfig);
+        mergedConfig1.updateFrom(mergedOverrideConfig1);
+
+        mergedConfig2.setTo(mergedConfig1);
+        mergedConfig2.updateFrom(mergedOverrideConfig2);
+
+        assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+        assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+        assertEquals(mergedRootConfig, root.getConfiguration());
+
+        assertEquals(childOverrideConfig1, child1.getOverrideConfiguration());
+        assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration());
+        assertEquals(mergedConfig1, child1.getConfiguration());
+
+        assertEquals(childOverrideConfig2, child2.getOverrideConfiguration());
+        assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration());
+        assertEquals(mergedConfig2, child2.getConfiguration());
+    }
+
+    @Test
+    public void testSetWindowingMode() throws Exception {
+        final TestConfigurationContainer root = new TestConfigurationContainer();
+        root.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+        final TestConfigurationContainer child = root.addChild();
+        child.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertEquals(WINDOWING_MODE_UNDEFINED, root.getWindowingMode());
+        assertEquals(WINDOWING_MODE_FREEFORM, child.getWindowingMode());
+
+        root.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        assertEquals(WINDOWING_MODE_FULLSCREEN, root.getWindowingMode());
+        assertEquals(WINDOWING_MODE_FREEFORM, child.getWindowingMode());
+    }
+
+    @Test
+    public void testSetActivityType() throws Exception {
+        final TestConfigurationContainer root = new TestConfigurationContainer();
+        root.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+        final TestConfigurationContainer child = root.addChild();
+        child.setActivityType(ACTIVITY_TYPE_STANDARD);
+        assertEquals(ACTIVITY_TYPE_UNDEFINED, root.getActivityType());
+        assertEquals(ACTIVITY_TYPE_STANDARD, child.getActivityType());
+
+        boolean gotException = false;
+        try {
+            // Can't change activity type once set.
+            child.setActivityType(ACTIVITY_TYPE_HOME);
+        } catch (IllegalStateException e) {
+            gotException = true;
+        }
+        assertTrue("Can't change activity type once set.", gotException);
+
+        // TODO: Commenting out for now until we figure-out a good way to test these rules that
+        // should only apply to system process.
+        /*
+        gotException = false;
+        try {
+            // Parent can't change child's activity type once set.
+            root.setActivityType(ACTIVITY_TYPE_HOME);
+        } catch (IllegalStateException e) {
+            gotException = true;
+        }
+        assertTrue("Parent can't change activity type once set.", gotException);
+        assertEquals(ACTIVITY_TYPE_HOME, root.getActivityType());
+
+        final TestConfigurationContainer child2 = new TestConfigurationContainer();
+        child2.setActivityType(ACTIVITY_TYPE_RECENTS);
+
+        gotException = false;
+        try {
+            // Can't re-parent to a different activity type.
+            root.addChild(child2);
+        } catch (IllegalStateException e) {
+            gotException = true;
+        }
+        assertTrue("Can't re-parent to a different activity type.", gotException);
+        */
+
+    }
+
+    @Test
+    public void testRegisterConfigurationChangeListener() throws Exception {
+        final TestConfigurationContainer container = new TestConfigurationContainer();
+        final TestConfigurationChangeListener listener = new TestConfigurationChangeListener();
+        final Configuration config = new Configuration();
+        config.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        config.windowConfiguration.setAppBounds(10, 10, 10, 10);
+        container.onOverrideConfigurationChanged(config);
+        container.registerConfigurationChangeListener(listener);
+        // Assert listener got the current config. of the container after it was registered.
+        assertEquals(config, listener.mOverrideConfiguration);
+        // Assert listener gets changes to override configuration.
+        container.onOverrideConfigurationChanged(EMPTY);
+        assertEquals(EMPTY, listener.mOverrideConfiguration);
+    }
+
+    /**
+     * Contains minimal implementation of {@link ConfigurationContainer}'s abstract behavior needed
+     * for testing.
+     */
+    private class TestConfigurationContainer
+            extends ConfigurationContainer<TestConfigurationContainer> {
+        private List<TestConfigurationContainer> mChildren = new ArrayList<>();
+        private TestConfigurationContainer mParent;
+
+        TestConfigurationContainer addChild(TestConfigurationContainer childContainer) {
+            childContainer.mParent = this;
+            childContainer.onParentChanged();
+            mChildren.add(childContainer);
+            return childContainer;
+        }
+
+        TestConfigurationContainer addChild() {
+            return addChild(new TestConfigurationContainer());
+        }
+
+        void removeChild(TestConfigurationContainer child) {
+            child.mParent = null;
+            child.onParentChanged();
+        }
+
+        @Override
+        protected int getChildCount() {
+            return mChildren.size();
+        }
+
+        @Override
+        protected TestConfigurationContainer getChildAt(int index) {
+            return mChildren.get(index);
+        }
+
+        @Override
+        protected ConfigurationContainer getParent() {
+            return mParent;
+        }
+    }
+
+    private class TestConfigurationChangeListener implements ConfigurationContainerListener {
+
+        final Configuration mOverrideConfiguration = new Configuration();
+
+        public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+            mOverrideConfiguration.setTo(overrideConfiguration);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
new file mode 100644
index 0000000..6769e40
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2017 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:com.android.server.wm.DimmerTests;
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DimmerTests extends WindowTestsBase {
+
+    private class TestWindowContainer extends WindowContainer<TestWindowContainer> {
+        final SurfaceControl mControl = mock(SurfaceControl.class);
+        final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class);
+
+        TestWindowContainer() {
+            super(sWm);
+        }
+
+        @Override
+        public SurfaceControl getSurfaceControl() {
+            return mControl;
+        }
+
+        @Override
+        public SurfaceControl.Transaction getPendingTransaction() {
+            return mTransaction;
+        }
+    }
+
+    private class MockSurfaceBuildingContainer extends WindowContainer<TestWindowContainer> {
+        final SurfaceSession mSession = new SurfaceSession();
+        final SurfaceControl mHostControl = mock(SurfaceControl.class);
+        final SurfaceControl.Transaction mHostTransaction = mock(SurfaceControl.Transaction.class);
+
+        MockSurfaceBuildingContainer() {
+            super(sWm);
+        }
+
+        class MockSurfaceBuilder extends SurfaceControl.Builder {
+            MockSurfaceBuilder(SurfaceSession ss) {
+                super(ss);
+            }
+
+            @Override
+            public SurfaceControl build() {
+                return mock(SurfaceControl.class);
+            }
+        }
+
+        @Override
+        SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+            return new MockSurfaceBuilder(mSession);
+        }
+
+        @Override
+        public SurfaceControl getSurfaceControl() {
+            return mHostControl;
+        }
+
+        @Override
+        public SurfaceControl.Transaction getPendingTransaction() {
+            return mHostTransaction;
+        }
+    }
+
+    private MockSurfaceBuildingContainer mHost;
+    private Dimmer mDimmer;
+    private SurfaceControl.Transaction mTransaction;
+    private Dimmer.SurfaceAnimatorStarter mSurfaceAnimatorStarter;
+
+    private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter {
+        @Override
+        public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
+                AnimationAdapter anim, boolean hidden) {
+            surfaceAnimator.mAnimationFinishedCallback.run();
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mHost = new MockSurfaceBuildingContainer();
+        mSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
+        mTransaction = mock(SurfaceControl.Transaction.class);
+        mDimmer = new Dimmer(mHost, mSurfaceAnimatorStarter);
+    }
+
+    @Test
+    public void testDimAboveNoChildCreatesSurface() throws Exception {
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, alpha);
+
+        SurfaceControl dimLayer = getDimLayer();
+
+        assertNotNull("Dimmer should have created a surface", dimLayer);
+
+        verify(mTransaction).setAlpha(dimLayer, alpha);
+        verify(mTransaction).setLayer(dimLayer, Integer.MAX_VALUE);
+    }
+
+    @Test
+    public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() throws Exception {
+        float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, alpha);
+        final SurfaceControl firstSurface = getDimLayer();
+
+        alpha = 0.9f;
+        mDimmer.dimAbove(mTransaction, alpha);
+
+        assertEquals(firstSurface, getDimLayer());
+        verify(mTransaction).setAlpha(firstSurface, 0.9f);
+    }
+
+    @Test
+    public void testUpdateDimsAppliesSize() throws Exception {
+        mDimmer.dimAbove(mTransaction, 0.8f);
+
+        int width = 100;
+        int height = 300;
+        Rect bounds = new Rect(0, 0, width, height);
+        mDimmer.updateDims(mTransaction, bounds);
+
+        verify(mTransaction).setSize(getDimLayer(), width, height);
+        verify(mTransaction).show(getDimLayer());
+    }
+
+    @Test
+    public void testDimAboveNoChildNotReset() throws Exception {
+        mDimmer.dimAbove(mTransaction, 0.8f);
+        SurfaceControl dimLayer = getDimLayer();
+        mDimmer.resetDimStates();
+
+        mDimmer.updateDims(mTransaction, new Rect());
+        verify(mTransaction).show(getDimLayer());
+        verify(dimLayer, never()).destroy();
+    }
+
+    @Test
+    public void testDimAboveWithChildCreatesSurfaceAboveChild() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, child, alpha);
+        SurfaceControl dimLayer = getDimLayer();
+
+        assertNotNull("Dimmer should have created a surface", dimLayer);
+
+        verify(mTransaction).setAlpha(dimLayer, alpha);
+        verify(mTransaction).setRelativeLayer(dimLayer, child.mControl, 1);
+    }
+
+    @Test
+    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimBelow(mTransaction, child, alpha);
+        SurfaceControl dimLayer = getDimLayer();
+
+        assertNotNull("Dimmer should have created a surface", dimLayer);
+
+        verify(mTransaction).setAlpha(dimLayer, alpha);
+        verify(mTransaction).setRelativeLayer(dimLayer, child.mControl, -1);
+    }
+
+    @Test
+    public void testDimBelowWithChildSurfaceDestroyedWhenReset() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, child, alpha);
+        SurfaceControl dimLayer = getDimLayer();
+        mDimmer.resetDimStates();
+
+        mDimmer.updateDims(mTransaction, new Rect());
+        verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any(
+                SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean());
+        verify(dimLayer).destroy();
+    }
+
+    @Test
+    public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, child, alpha);
+        SurfaceControl dimLayer = getDimLayer();
+        mDimmer.resetDimStates();
+        mDimmer.dimAbove(mTransaction, child, alpha);
+
+        mDimmer.updateDims(mTransaction, new Rect());
+        verify(mTransaction).show(dimLayer);
+        verify(dimLayer, never()).destroy();
+    }
+
+    @Test
+    public void testDimUpdateWhileDimming() throws Exception {
+        Rect bounds = new Rect();
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, child, alpha);
+
+        SurfaceControl dimLayer = getDimLayer();
+        bounds.set(0, 0, 10, 10);
+        mDimmer.updateDims(mTransaction, bounds);
+        verify(mTransaction, times(1)).show(dimLayer);
+        verify(mTransaction).setSize(dimLayer, bounds.width(), bounds.height());
+        verify(mTransaction).setPosition(dimLayer, 0, 0);
+
+        bounds.set(10, 10, 30, 30);
+        mDimmer.updateDims(mTransaction, bounds);
+        verify(mTransaction).setSize(dimLayer, bounds.width(), bounds.height());
+        verify(mTransaction).setPosition(dimLayer, 10, 10);
+    }
+
+    @Test
+    public void testRemoveDimImmediately() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        mDimmer.dimAbove(mTransaction, child, 1);
+        SurfaceControl dimLayer = getDimLayer();
+        mDimmer.updateDims(mTransaction, new Rect());
+        verify(mTransaction, times(1)).show(dimLayer);
+
+        reset(mSurfaceAnimatorStarter);
+        mDimmer.dontAnimateExit();
+        mDimmer.resetDimStates();
+        mDimmer.updateDims(mTransaction, new Rect());
+        verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any(
+                SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean());
+        verify(mTransaction).destroy(dimLayer);
+    }
+
+    private SurfaceControl getDimLayer() {
+        return mDimmer.mDimState.mDimLayer;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
new file mode 100644
index 0000000..ac196f9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2016 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.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.DisplayCutout.fromBoundingRect;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.annotation.SuppressLint;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.DisplayMetrics;
+import android.util.SparseIntArray;
+import android.view.DisplayCutout;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+import com.android.server.wm.utils.WmDisplayCutout;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Tests for the {@link DisplayContent} class.
+ *
+ * Build/Install/Run:
+ *  atest com.android.server.wm.DisplayContentTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DisplayContentTests extends WindowTestsBase {
+
+    @Test
+    @FlakyTest(bugId = 77772044)
+    public void testForAllWindows() throws Exception {
+        final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION,
+                mDisplayContent, "exiting app");
+        final AppWindowToken exitingAppToken = exitingAppWindow.mAppToken;
+        exitingAppToken.mIsExiting = true;
+        exitingAppToken.getTask().mStack.mExitingAppTokens.add(exitingAppToken);
+
+        assertForAllWindowsOrder(Arrays.asList(
+                mWallpaperWindow,
+                exitingAppWindow,
+                mChildAppWindowBelow,
+                mAppWindow,
+                mChildAppWindowAbove,
+                mDockedDividerWindow,
+                mStatusBarWindow,
+                mNavBarWindow,
+                mImeWindow,
+                mImeDialogWindow));
+    }
+
+    @Test
+    public void testForAllWindows_WithAppImeTarget() throws Exception {
+        final WindowState imeAppTarget =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
+
+        sWm.mInputMethodTarget = imeAppTarget;
+
+        assertForAllWindowsOrder(Arrays.asList(
+                mWallpaperWindow,
+                mChildAppWindowBelow,
+                mAppWindow,
+                mChildAppWindowAbove,
+                imeAppTarget,
+                mImeWindow,
+                mImeDialogWindow,
+                mDockedDividerWindow,
+                mStatusBarWindow,
+                mNavBarWindow));
+    }
+
+    @Test
+    public void testForAllWindows_WithChildWindowImeTarget() throws Exception {
+        sWm.mInputMethodTarget = mChildAppWindowAbove;
+
+        assertForAllWindowsOrder(Arrays.asList(
+                mWallpaperWindow,
+                mChildAppWindowBelow,
+                mAppWindow,
+                mChildAppWindowAbove,
+                mImeWindow,
+                mImeDialogWindow,
+                mDockedDividerWindow,
+                mStatusBarWindow,
+                mNavBarWindow));
+    }
+
+    @Test
+    public void testForAllWindows_WithStatusBarImeTarget() throws Exception {
+        sWm.mInputMethodTarget = mStatusBarWindow;
+
+        assertForAllWindowsOrder(Arrays.asList(
+                mWallpaperWindow,
+                mChildAppWindowBelow,
+                mAppWindow,
+                mChildAppWindowAbove,
+                mDockedDividerWindow,
+                mStatusBarWindow,
+                mImeWindow,
+                mImeDialogWindow,
+                mNavBarWindow));
+    }
+
+    @Test
+    public void testForAllWindows_WithInBetweenWindowToken() throws Exception {
+        // This window is set-up to be z-ordered between some windows that go in the same token like
+        // the nav bar and status bar.
+        final WindowState voiceInteractionWindow = createWindow(null, TYPE_VOICE_INTERACTION,
+                mDisplayContent, "voiceInteractionWindow");
+
+        assertForAllWindowsOrder(Arrays.asList(
+                mWallpaperWindow,
+                mChildAppWindowBelow,
+                mAppWindow,
+                mChildAppWindowAbove,
+                mDockedDividerWindow,
+                voiceInteractionWindow,
+                mStatusBarWindow,
+                mNavBarWindow,
+                mImeWindow,
+                mImeDialogWindow));
+    }
+
+    @Test
+    public void testComputeImeTarget() throws Exception {
+        // Verify that an app window can be an ime target.
+        final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin");
+        appWin.setHasSurface(true);
+        assertTrue(appWin.canBeImeTarget());
+        WindowState imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */);
+        assertEquals(appWin, imeTarget);
+        appWin.mHidden = false;
+
+        // Verify that an child window can be an ime target.
+        final WindowState childWin = createWindow(appWin,
+                TYPE_APPLICATION_ATTACHED_DIALOG, "childWin");
+        childWin.setHasSurface(true);
+        assertTrue(childWin.canBeImeTarget());
+        imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */);
+        assertEquals(childWin, imeTarget);
+    }
+
+    /**
+     * This tests stack movement between displays and proper stack's, task's and app token's display
+     * container references updates.
+     */
+    @Test
+    public void testMoveStackBetweenDisplays() throws Exception {
+        // Create a second display.
+        final DisplayContent dc = createNewDisplay();
+
+        // Add stack with activity.
+        final TaskStack stack = createTaskStackOnDisplay(dc);
+        assertEquals(dc.getDisplayId(), stack.getDisplayContent().getDisplayId());
+        assertEquals(dc, stack.getParent().getParent());
+        assertEquals(dc, stack.getDisplayContent());
+
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final WindowTestUtils.TestAppWindowToken token = WindowTestUtils.createTestAppWindowToken(
+                dc);
+        task.addChild(token, 0);
+        assertEquals(dc, task.getDisplayContent());
+        assertEquals(dc, token.getDisplayContent());
+
+        // Move stack to first display.
+        mDisplayContent.moveStackToDisplay(stack, true /* onTop */);
+        assertEquals(mDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId());
+        assertEquals(mDisplayContent, stack.getParent().getParent());
+        assertEquals(mDisplayContent, stack.getDisplayContent());
+        assertEquals(mDisplayContent, task.getDisplayContent());
+        assertEquals(mDisplayContent, token.getDisplayContent());
+    }
+
+    /**
+     * This tests override configuration updates for display content.
+     */
+    @Test
+    public void testDisplayOverrideConfigUpdate() throws Exception {
+        final int displayId = mDisplayContent.getDisplayId();
+        final Configuration currentOverrideConfig = mDisplayContent.getOverrideConfiguration();
+
+        // Create new, slightly changed override configuration and apply it to the display.
+        final Configuration newOverrideConfig = new Configuration(currentOverrideConfig);
+        newOverrideConfig.densityDpi += 120;
+        newOverrideConfig.fontScale += 0.3;
+
+        sWm.setNewDisplayOverrideConfiguration(newOverrideConfig, displayId);
+
+        // Check that override config is applied.
+        assertEquals(newOverrideConfig, mDisplayContent.getOverrideConfiguration());
+    }
+
+    /**
+     * This tests global configuration updates when default display config is updated.
+     */
+    @Test
+    public void testDefaultDisplayOverrideConfigUpdate() throws Exception {
+        final Configuration currentConfig = mDisplayContent.getConfiguration();
+
+        // Create new, slightly changed override configuration and apply it to the display.
+        final Configuration newOverrideConfig = new Configuration(currentConfig);
+        newOverrideConfig.densityDpi += 120;
+        newOverrideConfig.fontScale += 0.3;
+
+        sWm.setNewDisplayOverrideConfiguration(newOverrideConfig, DEFAULT_DISPLAY);
+
+        // Check that global configuration is updated, as we've updated default display's config.
+        Configuration globalConfig = sWm.mRoot.getConfiguration();
+        assertEquals(newOverrideConfig.densityDpi, globalConfig.densityDpi);
+        assertEquals(newOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
+
+        // Return back to original values.
+        sWm.setNewDisplayOverrideConfiguration(currentConfig, DEFAULT_DISPLAY);
+        globalConfig = sWm.mRoot.getConfiguration();
+        assertEquals(currentConfig.densityDpi, globalConfig.densityDpi);
+        assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
+    }
+
+    /**
+     * Tests tapping on a stack in different display results in window gaining focus.
+     */
+    @Test
+    public void testInputEventBringsCorrectDisplayInFocus() throws Exception {
+        DisplayContent dc0 = sWm.getDefaultDisplayContentLocked();
+        // Create a second display
+        final DisplayContent dc1 = createNewDisplay();
+
+        // Add stack with activity.
+        final TaskStack stack0 = createTaskStackOnDisplay(dc0);
+        final Task task0 = createTaskInStack(stack0, 0 /* userId */);
+        final WindowTestUtils.TestAppWindowToken token =
+                WindowTestUtils.createTestAppWindowToken(dc0);
+        task0.addChild(token, 0);
+        dc0.mTapDetector = new TaskTapPointerEventListener(sWm, dc0);
+        sWm.registerPointerEventListener(dc0.mTapDetector);
+        final TaskStack stack1 = createTaskStackOnDisplay(dc1);
+        final Task task1 = createTaskInStack(stack1, 0 /* userId */);
+        final WindowTestUtils.TestAppWindowToken token1 =
+                WindowTestUtils.createTestAppWindowToken(dc0);
+        task1.addChild(token1, 0);
+        dc1.mTapDetector = new TaskTapPointerEventListener(sWm, dc0);
+        sWm.registerPointerEventListener(dc1.mTapDetector);
+
+        // tap on primary display (by sending ACTION_DOWN followed by ACTION_UP)
+        DisplayMetrics dm0 = dc0.getDisplayMetrics();
+        dc0.mTapDetector.onPointerEvent(
+                createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, true));
+        dc0.mTapDetector.onPointerEvent(
+                createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, false));
+
+        // Check focus is on primary display.
+        assertEquals(sWm.mCurrentFocus, dc0.findFocusedWindow());
+
+        // Tap on secondary display
+        DisplayMetrics dm1 = dc1.getDisplayMetrics();
+        dc1.mTapDetector.onPointerEvent(
+                createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, true));
+        dc1.mTapDetector.onPointerEvent(
+                createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, false));
+
+        // Check focus is on secondary.
+        assertEquals(sWm.mCurrentFocus, dc1.findFocusedWindow());
+    }
+
+    @Test
+    public void testFocusedWindowMultipleDisplays() throws Exception {
+        // Create a focusable window and check that focus is calculated correctly
+        final WindowState window1 =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1");
+        assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+
+        // Check that a new display doesn't affect focus
+        final DisplayContent dc = createNewDisplay();
+        assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+
+        // Add a window to the second display, and it should be focused
+        final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2");
+        assertEquals(window2, sWm.mRoot.computeFocusedWindow());
+
+        // Move the first window to the to including parents, and make sure focus is updated
+        window1.getParent().positionChildAt(POSITION_TOP, window1, true);
+        assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+    }
+
+    @Test
+    public void testKeyguard_preventsSecondaryDisplayFocus() throws Exception {
+        final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR,
+                sWm.getDefaultDisplayContentLocked(), "keyguard");
+        assertEquals(keyguard, sWm.mRoot.computeFocusedWindow());
+
+        // Add a window to a second display, and it should be focused
+        final DisplayContent dc = createNewDisplay();
+        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
+        assertEquals(win, sWm.mRoot.computeFocusedWindow());
+
+        mWmRule.getWindowManagerPolicy().keyguardShowingAndNotOccluded = true;
+        assertEquals(keyguard, sWm.mRoot.computeFocusedWindow());
+    }
+
+    /**
+     * This tests setting the maximum ui width on a display.
+     */
+    @Test
+    public void testMaxUiWidth() throws Exception {
+        final int baseWidth = 1440;
+        final int baseHeight = 2560;
+        final int baseDensity = 300;
+
+        mDisplayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity);
+
+        final int maxWidth = 300;
+        final int resultingHeight = (maxWidth * baseHeight) / baseWidth;
+        final int resultingDensity = (maxWidth * baseDensity) / baseWidth;
+
+        mDisplayContent.setMaxUiWidth(maxWidth);
+        verifySizes(mDisplayContent, maxWidth, resultingHeight, resultingDensity);
+
+        // Assert setting values again does not change;
+        mDisplayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity);
+        verifySizes(mDisplayContent, maxWidth, resultingHeight, resultingDensity);
+
+        final int smallerWidth = 200;
+        final int smallerHeight = 400;
+        final int smallerDensity = 100;
+
+        // Specify smaller dimension, verify that it is honored
+        mDisplayContent.updateBaseDisplayMetrics(smallerWidth, smallerHeight, smallerDensity);
+        verifySizes(mDisplayContent, smallerWidth, smallerHeight, smallerDensity);
+
+        // Verify that setting the max width to a greater value than the base width has no effect
+        mDisplayContent.setMaxUiWidth(maxWidth);
+        verifySizes(mDisplayContent, smallerWidth, smallerHeight, smallerDensity);
+    }
+
+    /**
+     * This test enforces that the pinned stack is always kept as the top stack.
+     */
+    @Test
+    public void testPinnedStackLocation() {
+        final TaskStack pinnedStack = createStackControllerOnStackOnDisplay(
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer;
+        // Ensure that the pinned stack is the top stack
+        assertEquals(pinnedStack, mDisplayContent.getPinnedStack());
+        assertEquals(pinnedStack, mDisplayContent.getTopStack());
+        // By default, this should try to create a new stack on top
+        final TaskStack otherStack = createTaskStackOnDisplay(mDisplayContent);
+        // Ensure that the other stack is on the display.
+        assertEquals(mDisplayContent, otherStack.getDisplayContent());
+        // Ensure that the pinned stack is still on top
+        assertEquals(pinnedStack, mDisplayContent.getTopStack());
+    }
+
+    /**
+     * Test that WM does not report displays to AM that are pending to be removed.
+     */
+    @Test
+    public void testDontReportDeferredRemoval() {
+        // Create a display and add an animating window to it.
+        final DisplayContent dc = createNewDisplay();
+        final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
+        window.mAnimatingExit = true;
+        // Request display removal, it should be deferred.
+        dc.removeIfPossible();
+        // Request ordered display ids from WM.
+        final SparseIntArray orderedDisplayIds = new SparseIntArray();
+        sWm.getDisplaysInFocusOrder(orderedDisplayIds);
+        // Make sure that display that is marked for removal is not reported.
+        assertEquals(-1, orderedDisplayIds.indexOfValue(dc.getDisplayId()));
+    }
+
+    @Test
+    public void testDisplayCutout_rot0() throws Exception {
+        synchronized (sWm.getWindowManagerLock()) {
+            final DisplayContent dc = createNewDisplay();
+            dc.mInitialDisplayWidth = 200;
+            dc.mInitialDisplayHeight = 400;
+            Rect r = new Rect(80, 0, 120, 10);
+            final DisplayCutout cutout = new WmDisplayCutout(
+                    fromBoundingRect(r.left, r.top, r.right, r.bottom), null)
+                    .computeSafeInsets(200, 400).getDisplayCutout();
+
+            dc.mInitialDisplayCutout = cutout;
+            dc.setRotation(Surface.ROTATION_0);
+            dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo.
+
+            assertEquals(cutout, dc.getDisplayInfo().displayCutout);
+        }
+    }
+
+    @Test
+    public void testDisplayCutout_rot90() throws Exception {
+        synchronized (sWm.getWindowManagerLock()) {
+            final DisplayContent dc = createNewDisplay();
+            dc.mInitialDisplayWidth = 200;
+            dc.mInitialDisplayHeight = 400;
+            Rect r1 = new Rect(80, 0, 120, 10);
+            final DisplayCutout cutout = new WmDisplayCutout(
+                    fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom), null)
+                    .computeSafeInsets(200, 400).getDisplayCutout();
+
+            dc.mInitialDisplayCutout = cutout;
+            dc.setRotation(Surface.ROTATION_90);
+            dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo.
+
+            final Rect r = new Rect(0, 80, 10, 120);
+            assertEquals(new WmDisplayCutout(
+                    fromBoundingRect(r.left, r.top, r.right, r.bottom), null)
+                    .computeSafeInsets(400, 200).getDisplayCutout(), dc.getDisplayInfo().displayCutout);
+        }
+    }
+
+    @Test
+    public void testLayoutSeq_assignedDuringLayout() throws Exception {
+        synchronized (sWm.getWindowManagerLock()) {
+
+            final DisplayContent dc = createNewDisplay();
+            final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
+
+            dc.setLayoutNeeded();
+            dc.performLayout(true /* initial */, false /* updateImeWindows */);
+
+            assertThat(win.mLayoutSeq, is(dc.mLayoutSeq));
+        }
+    }
+
+    @Test
+    @SuppressLint("InlinedApi")
+    public void testOrientationDefinedByKeyguard() {
+        final DisplayContent dc = createNewDisplay();
+        // Create a window that requests landscape orientation. It will define device orientation
+        // by default.
+        final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
+        window.mAppToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+        final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR, dc, "keyguard");
+        keyguard.mHasSurface = true;
+        keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+        assertEquals("Screen orientation must be defined by the app window by default",
+                SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation());
+
+        keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_PORTRAIT;
+        assertEquals("Visible keyguard must influence device orientation",
+                SCREEN_ORIENTATION_PORTRAIT, dc.getOrientation());
+
+        sWm.setKeyguardGoingAway(true);
+        assertEquals("Keyguard that is going away must not influence device orientation",
+                SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation());
+    }
+
+    @Test
+    public void testDisableDisplayInfoOverrideFromWindowManager() {
+        final DisplayContent dc = createNewDisplay();
+
+        assertTrue(dc.mShouldOverrideDisplayConfiguration);
+        sWm.dontOverrideDisplayInfo(dc.getDisplayId());
+
+        assertFalse(dc.mShouldOverrideDisplayConfiguration);
+        verify(sWm.mDisplayManagerInternal, times(1))
+                .setDisplayInfoOverrideFromWindowManager(dc.getDisplayId(), null);
+    }
+
+    private static void verifySizes(DisplayContent displayContent, int expectedBaseWidth,
+                             int expectedBaseHeight, int expectedBaseDensity) {
+        assertEquals(displayContent.mBaseDisplayWidth, expectedBaseWidth);
+        assertEquals(displayContent.mBaseDisplayHeight, expectedBaseHeight);
+        assertEquals(displayContent.mBaseDisplayDensity, expectedBaseDensity);
+    }
+
+    private void assertForAllWindowsOrder(List<WindowState> expectedWindowsBottomToTop) {
+        final LinkedList<WindowState> actualWindows = new LinkedList<>();
+
+        // Test forward traversal.
+        mDisplayContent.forAllWindows(actualWindows::addLast, false /* traverseTopToBottom */);
+        assertThat("bottomToTop", actualWindows, is(expectedWindowsBottomToTop));
+
+        actualWindows.clear();
+
+        // Test backward traversal.
+        mDisplayContent.forAllWindows(actualWindows::addLast, true /* traverseTopToBottom */);
+        assertThat("topToBottom", actualWindows, is(reverseList(expectedWindowsBottomToTop)));
+    }
+
+    private static List<WindowState> reverseList(List<WindowState> list) {
+        final ArrayList<WindowState> result = new ArrayList<>(list);
+        Collections.reverse(result);
+        return result;
+    }
+
+    private MotionEvent createTapEvent(float x, float y, boolean isDownEvent) {
+        final long downTime = SystemClock.uptimeMillis();
+        final long eventTime = SystemClock.uptimeMillis() + 100;
+        final int metaState = 0;
+
+        return MotionEvent.obtain(
+                downTime,
+                eventTime,
+                isDownEvent ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP,
+                x,
+                y,
+                metaState);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
new file mode 100644
index 0000000..a09656c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2017 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.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ClipData;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManagerInternal;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.InputChannel;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.View;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link DragDropController} class.
+ *
+ * atest com.android.server.wm.DragDropControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class DragDropControllerTests extends WindowTestsBase {
+    private static final int TIMEOUT_MS = 3000;
+    private TestDragDropController mTarget;
+    private WindowState mWindow;
+    private IBinder mToken;
+
+    static class TestDragDropController extends DragDropController {
+        @GuardedBy("sWm.mWindowMap")
+        private Runnable mCloseCallback;
+
+        TestDragDropController(WindowManagerService service, Looper looper) {
+            super(service, looper);
+        }
+
+        void setOnClosedCallbackLocked(Runnable runnable) {
+            assertTrue(dragDropActiveLocked());
+            mCloseCallback = runnable;
+        }
+
+        @Override
+        void onDragStateClosedLocked(DragState dragState) {
+            super.onDragStateClosedLocked(dragState);
+            if (mCloseCallback != null) {
+                mCloseCallback.run();
+                mCloseCallback = null;
+            }
+        }
+    }
+
+    /**
+     * Creates a window state which can be used as a drop target.
+     */
+    private WindowState createDropTargetWindow(String name, int ownerId) {
+        final WindowTestUtils.TestAppWindowToken token = WindowTestUtils.createTestAppWindowToken(
+                mDisplayContent);
+        final TaskStack stack = createStackControllerOnStackOnDisplay(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer;
+        final Task task = createTaskInStack(stack, ownerId);
+        task.addChild(token, 0);
+
+        final WindowState window = createWindow(
+                null, TYPE_BASE_APPLICATION, token, name, ownerId, false);
+        window.mInputChannel = new InputChannel();
+        window.mHasSurface = true;
+        return window;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        final UserManagerInternal userManager = mock(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, userManager);
+
+        super.setUp();
+
+        mTarget = new TestDragDropController(sWm, sWm.mH.getLooper());
+        mDisplayContent = spy(mDisplayContent);
+        mWindow = createDropTargetWindow("Drag test window", 0);
+        when(mDisplayContent.getTouchableWinAtPointLocked(0, 0)).thenReturn(mWindow);
+        when(sWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true);
+
+        synchronized (sWm.mWindowMap) {
+            sWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        final CountDownLatch latch;
+        synchronized (sWm.mWindowMap) {
+            if (!mTarget.dragDropActiveLocked()) {
+                return;
+            }
+            if (mToken != null) {
+                mTarget.cancelDragAndDrop(mToken);
+            }
+            latch = new CountDownLatch(1);
+            mTarget.setOnClosedCallbackLocked(() -> {
+                latch.countDown();
+            });
+        }
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testDragFlow() throws Exception {
+        dragFlow(0, ClipData.newPlainText("label", "Test"), 0, 0);
+    }
+
+    @Test
+    public void testPerformDrag_NullDataWithGrantUri() throws Exception {
+        dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0);
+    }
+
+    @Test
+    public void testPerformDrag_NullDataToOtherUser() throws Exception {
+        final WindowState otherUsersWindow =
+                createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE);
+        when(mDisplayContent.getTouchableWinAtPointLocked(10, 10))
+                .thenReturn(otherUsersWindow);
+
+        dragFlow(0, null, 10, 10);
+    }
+
+    private void dragFlow(int flag, ClipData data, float dropX, float dropY) {
+        final SurfaceSession appSession = new SurfaceSession();
+        try {
+            final SurfaceControl surface = new SurfaceControl.Builder(appSession)
+                    .setName("drag surface")
+                    .setSize(100, 100)
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .build();
+
+            assertTrue(sWm.mInputManager.transferTouchFocus(null, null));
+            mToken = mTarget.performDrag(
+                    new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
+                    data);
+            assertNotNull(mToken);
+
+            mTarget.handleMotionEvent(false, dropX, dropY);
+            mToken = mWindow.mClient.asBinder();
+        } finally {
+            appSession.kill();
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/PinnedStackControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/PinnedStackControllerTest.java
new file mode 100644
index 0000000..96745fa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/PinnedStackControllerTest.java
@@ -0,0 +1,63 @@
+package com.android.server.wm;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.IPinnedStackListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class PinnedStackControllerTest extends WindowTestsBase {
+
+    @Mock private IPinnedStackListener mIPinnedStackListener;
+    @Mock private IPinnedStackListener.Stub mIPinnedStackListenerStub;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        when(mIPinnedStackListener.asBinder()).thenReturn(mIPinnedStackListenerStub);
+    }
+
+    @Test
+    public void setShelfHeight_shelfVisibilityChangedTriggered() throws RemoteException {
+        sWm.mSupportsPictureInPicture = true;
+        sWm.registerPinnedStackListener(DEFAULT_DISPLAY, mIPinnedStackListener);
+
+        verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0);
+        verify(mIPinnedStackListener).onShelfVisibilityChanged(false, 0);
+        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
+                eq(false), anyInt());
+        verify(mIPinnedStackListener).onActionsChanged(any());
+        verify(mIPinnedStackListener).onMinimizedStateChanged(anyBoolean());
+
+        reset(mIPinnedStackListener);
+
+        final int SHELF_HEIGHT = 300;
+
+        sWm.setShelfHeight(true, SHELF_HEIGHT);
+        verify(mIPinnedStackListener).onShelfVisibilityChanged(true, SHELF_HEIGHT);
+        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
+                eq(true), anyInt());
+        verify(mIPinnedStackListener, never()).onImeVisibilityChanged(anyBoolean(), anyInt());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java
new file mode 100644
index 0000000..a2af9b8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
+import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.os.Binder;
+import android.os.IInterface;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.IRecentsAnimationRunner;
+import android.view.SurfaceControl;
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * atest FrameworksServicesTests:com.android.server.wm.RecentsAnimationControllerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RecentsAnimationControllerTest extends WindowTestsBase {
+
+    @Mock SurfaceControl mMockLeash;
+    @Mock SurfaceControl.Transaction mMockTransaction;
+    @Mock OnAnimationFinishedCallback mFinishedCallback;
+    @Mock IRecentsAnimationRunner mMockRunner;
+    @Mock RecentsAnimationController.RecentsAnimationCallbacks mAnimationCallbacks;
+    private RecentsAnimationController mController;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        when(mMockRunner.asBinder()).thenReturn(new Binder());
+        mController = new RecentsAnimationController(sWm, mMockRunner, mAnimationCallbacks,
+                DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testRemovedBeforeStarted_expectCanceled() throws Exception {
+        final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        AnimationAdapter adapter = mController.addAnimation(appWindow.getTask(),
+                false /* isRecentTaskInvisible */);
+        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+
+        // Remove the app window so that the animation target can not be created
+        appWindow.removeImmediately();
+        mController.startAnimation();
+
+        // Verify that the finish callback to reparent the leash is called
+        verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+        // Verify the animation canceled callback to the app was made
+        verify(mMockRunner).onAnimationCanceled();
+        verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
+    }
+
+    @Test
+    public void testCancelAfterRemove_expectIgnored() throws Exception {
+        final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        AnimationAdapter adapter = mController.addAnimation(appWindow.getTask(),
+                false /* isRecentTaskInvisible */);
+        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+
+        // Remove the app window so that the animation target can not be created
+        appWindow.removeImmediately();
+        mController.startAnimation();
+        mController.cleanupAnimation(REORDER_KEEP_IN_PLACE);
+        try {
+            mController.cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "test");
+        } catch (Exception e) {
+            fail("Unexpected failure when canceling animation after finishing it");
+        }
+    }
+
+    private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
+        verify(binder, atLeast(0)).asBinder();
+        verifyNoMoreInteractions(binder);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java
new file mode 100644
index 0000000..95361f0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.IInterface;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * atest FrameworksServicesTests:com.android.server.wm.RemoteAnimationControllerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RemoteAnimationControllerTest extends WindowTestsBase {
+
+    @Mock SurfaceControl mMockLeash;
+    @Mock Transaction mMockTransaction;
+    @Mock OnAnimationFinishedCallback mFinishedCallback;
+    @Mock IRemoteAnimationRunner mMockRunner;
+    private RemoteAnimationAdapter mAdapter;
+    private RemoteAnimationController mController;
+    private final OffsettableClock mClock = new OffsettableClock.Stopped();
+    private TestHandler mHandler;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        when(mMockRunner.asBinder()).thenReturn(new Binder());
+        mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50);
+        mAdapter.setCallingPid(123);
+        sWm.mH.runWithScissors(() -> {
+            mHandler = new TestHandler(null, mClock);
+        }, 0);
+        mController = new RemoteAnimationController(sWm, mAdapter, mHandler);
+    }
+
+    @Test
+    public void testRun() throws Exception {
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        sWm.mOpeningApps.add(win.mAppToken);
+        try {
+            final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
+                    new Point(50, 100), new Rect(50, 100, 150, 150));
+            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            mController.goodToGo();
+            sWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+            assertEquals(1, appsCaptor.getValue().length);
+            final RemoteAnimationTarget app = appsCaptor.getValue()[0];
+            assertEquals(new Point(50, 100), app.position);
+            assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds);
+            assertEquals(win.mAppToken.getPrefixOrderIndex(), app.prefixOrderIndex);
+            assertEquals(win.mAppToken.getTask().mTaskId, app.taskId);
+            assertEquals(mMockLeash, app.leash);
+            assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect);
+            assertEquals(false, app.isTranslucent);
+            verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex);
+            verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y);
+            verify(mMockTransaction).setWindowCrop(mMockLeash, new Rect(0, 0, 100, 50));
+
+            finishedCaptor.getValue().onAnimationFinished();
+            verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+        } finally {
+            sWm.mOpeningApps.clear();
+        }
+    }
+
+    @Test
+    public void testCancel() throws Exception {
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150));
+        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+        mController.goodToGo();
+
+        adapter.onAnimationCancelled(mMockLeash);
+        verify(mMockRunner).onAnimationCancelled();
+    }
+
+    @Test
+    public void testTimeout() throws Exception {
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150));
+        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+        mController.goodToGo();
+
+        mClock.fastForward(2500);
+        mHandler.timeAdvance();
+
+        verify(mMockRunner).onAnimationCancelled();
+        verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+    }
+
+    @Test
+    public void testTimeout_scaled() throws Exception {
+        sWm.setAnimationScale(2, 5.0f);
+        try{
+            final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+            final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
+                    new Point(50, 100), new Rect(50, 100, 150, 150));
+            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            mController.goodToGo();
+
+            mClock.fastForward(2500);
+            mHandler.timeAdvance();
+
+            verify(mMockRunner, never()).onAnimationCancelled();
+
+            mClock.fastForward(10000);
+            mHandler.timeAdvance();
+
+            verify(mMockRunner).onAnimationCancelled();
+            verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+        } finally {
+            sWm.setAnimationScale(2, 1.0f);
+        }
+
+    }
+
+    @Test
+    public void testZeroAnimations() throws Exception {
+        mController.goodToGo();
+        verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
+    }
+
+    @Test
+    public void testNotReallyStarted() throws Exception {
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        mController.createAnimationAdapter(win.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150));
+        mController.goodToGo();
+        verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
+    }
+
+    @Test
+    public void testOneNotStarted() throws Exception {
+        final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
+        final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2");
+        mController.createAnimationAdapter(win1.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150));
+        final AnimationAdapter adapter = mController.createAnimationAdapter(win2.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150));
+        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+        mController.goodToGo();
+        sWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+        final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+        final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+        verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+        assertEquals(1, appsCaptor.getValue().length);
+        assertEquals(mMockLeash, appsCaptor.getValue()[0].leash);
+    }
+
+    @Test
+    public void testRemovedBeforeStarted() throws Exception {
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150));
+        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+        win.mAppToken.removeImmediately();
+        mController.goodToGo();
+        verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
+        verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+    }
+
+    private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
+        verify(binder, atLeast(0)).asBinder();
+        verifyNoMoreInteractions(binder);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/RootWindowContainerTests.java
new file mode 100644
index 0000000..204e26c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -0,0 +1,52 @@
+package com.android.server.wm;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the {@link RootWindowContainer} class.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:com.android.server.wm.RootWindowContainerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RootWindowContainerTests extends WindowTestsBase {
+    @Test
+    public void testSetDisplayOverrideConfigurationIfNeeded() throws Exception {
+        synchronized (sWm.mWindowMap) {
+            // Add first stack we expect to be updated with configuration change.
+            final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
+            stack.getOverrideConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 5, 5));
+
+            // Add second task that will be set for deferred removal that should not be returned
+            // with the configuration change.
+            final TaskStack deferredDeletedStack = createTaskStackOnDisplay(mDisplayContent);
+            deferredDeletedStack.getOverrideConfiguration().windowConfiguration.setBounds(
+                    new Rect(0, 0, 5, 5));
+            deferredDeletedStack.mDeferRemoval = true;
+
+            final Configuration override = new Configuration(
+                    mDisplayContent.getOverrideConfiguration());
+            override.windowConfiguration.setBounds(new Rect(0, 0, 10, 10));
+
+            // Set display override.
+            final int[] results = sWm.mRoot.setDisplayOverrideConfigurationIfNeeded(override,
+                    mDisplayContent.getDisplayId());
+
+            // Ensure only first stack is returned.
+            assertTrue(results.length == 1);
+            assertTrue(results[0] == stack.mStackId);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
new file mode 100644
index 0000000..a2ccee4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2017 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.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.graphics.Color.RED;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Gravity.BOTTOM;
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.RIGHT;
+import static android.view.Gravity.TOP;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static org.junit.Assert.assertEquals;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.function.BooleanSupplier;
+
+/**
+ * Tests for the {@link android.view.WindowManager.LayoutParams#PRIVATE_FLAG_IS_SCREEN_DECOR} flag.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:com.android.server.wm.ScreenDecorWindowTests
+ */
+// TODO: Add test for FLAG_FULLSCREEN which hides the status bar and also other flags.
+// TODO: Test non-Activity windows.
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ScreenDecorWindowTests {
+
+    private final Context mContext = InstrumentationRegistry.getTargetContext();
+    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+    private WindowManager mWm;
+    private ArrayList<View> mWindows = new ArrayList<>();
+
+    private Activity mTestActivity;
+    private VirtualDisplay mDisplay;
+    private ImageReader mImageReader;
+
+    private int mDecorThickness;
+    private int mHalfDecorThickness;
+
+    @Before
+    public void setUp() {
+        final Pair<VirtualDisplay, ImageReader> result = createDisplay();
+        mDisplay = result.first;
+        mImageReader = result.second;
+        final Display display = mDisplay.getDisplay();
+        final Context dContext = mContext.createDisplayContext(display);
+        mWm = dContext.getSystemService(WindowManager.class);
+        mTestActivity = startActivityOnDisplay(TestActivity.class, display.getDisplayId());
+        final Point size = new Point();
+        mDisplay.getDisplay().getRealSize(size);
+        mDecorThickness = Math.min(size.x, size.y) / 3;
+        mHalfDecorThickness = mDecorThickness / 2;
+    }
+
+    @After
+    public void tearDown() {
+        while (!mWindows.isEmpty()) {
+            removeWindow(mWindows.get(0));
+        }
+        finishActivity(mTestActivity);
+        mDisplay.release();
+        mImageReader.close();
+    }
+
+    @Test
+    public void testScreenSides() throws Exception {
+        // Decor on top
+        final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness);
+
+        // Decor at the bottom
+        updateWindow(decorWindow, BOTTOM, MATCH_PARENT, mDecorThickness, 0, 0);
+        assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mDecorThickness);
+
+        // Decor to the left
+        updateWindow(decorWindow, LEFT, mDecorThickness, MATCH_PARENT, 0, 0);
+        assertInsetGreaterOrEqual(mTestActivity, LEFT, mDecorThickness);
+
+        // Decor to the right
+        updateWindow(decorWindow, RIGHT, mDecorThickness, MATCH_PARENT, 0, 0);
+        assertInsetGreaterOrEqual(mTestActivity, RIGHT, mDecorThickness);
+    }
+
+    @Test
+    public void testMultipleDecors() throws Exception {
+        // Test 2 decor windows on-top.
+        createDecorWindow(TOP, MATCH_PARENT, mHalfDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, TOP, mHalfDecorThickness);
+        createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness);
+
+        // And one at the bottom.
+        createDecorWindow(BOTTOM, MATCH_PARENT, mHalfDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mHalfDecorThickness);
+    }
+
+    @Test
+    public void testFlagChange() throws Exception {
+        WindowInsets initialInsets = getInsets(mTestActivity);
+
+        final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
+        assertTopInsetEquals(mTestActivity, mDecorThickness);
+
+        updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness,
+                0, PRIVATE_FLAG_IS_SCREEN_DECOR);
+
+        // TODO: fix test and re-enable assertion.
+        // initialInsets was not actually immutable and just updated to the current insets,
+        // meaning this assertion never actually tested anything. Now that WindowInsets actually is
+        // immutable, it turns out the test was broken.
+        // assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop());
+
+        updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness,
+                PRIVATE_FLAG_IS_SCREEN_DECOR, PRIVATE_FLAG_IS_SCREEN_DECOR);
+        assertTopInsetEquals(mTestActivity, mDecorThickness);
+    }
+
+    @Test
+    public void testRemoval() throws Exception {
+        WindowInsets initialInsets = getInsets(mTestActivity);
+
+        final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
+        assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness);
+
+        removeWindow(decorWindow);
+        assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop());
+    }
+
+    private View createDecorWindow(int gravity, int width, int height) {
+        return createWindow("decorWindow", gravity, width, height, RED,
+                FLAG_LAYOUT_IN_SCREEN, PRIVATE_FLAG_IS_SCREEN_DECOR);
+    }
+
+    private View createWindow(String name, int gravity, int width, int height, int color, int flags,
+            int privateFlags) {
+
+        final View[] viewHolder = new View[1];
+        final int finalFlag = flags
+                | FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE;
+
+        // Needs to run on the UI thread.
+        Handler.getMain().runWithScissors(() -> {
+            final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                    width, height, TYPE_APPLICATION_OVERLAY, finalFlag, PixelFormat.OPAQUE);
+            lp.gravity = gravity;
+            lp.privateFlags |= privateFlags;
+
+            final TextView view = new TextView(mContext);
+            view.setText("ScreenDecorWindowTests - " + name);
+            view.setBackgroundColor(color);
+            mWm.addView(view, lp);
+            mWindows.add(view);
+            viewHolder[0] = view;
+        }, 0);
+
+        waitForIdle();
+        return viewHolder[0];
+    }
+
+    private void updateWindow(View v, int gravity, int width, int height,
+            int privateFlags, int privateFlagsMask) {
+        // Needs to run on the UI thread.
+        Handler.getMain().runWithScissors(() -> {
+            final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) v.getLayoutParams();
+            lp.gravity = gravity;
+            lp.width = width;
+            lp.height = height;
+            setPrivateFlags(lp, privateFlags, privateFlagsMask);
+
+            mWm.updateViewLayout(v, lp);
+        }, 0);
+
+        waitForIdle();
+    }
+
+    private void removeWindow(View v) {
+        Handler.getMain().runWithScissors(() -> mWm.removeView(v), 0);
+        mWindows.remove(v);
+        waitForIdle();
+    }
+
+    private WindowInsets getInsets(Activity a) {
+        return new WindowInsets(a.getWindow().getDecorView().getRootWindowInsets());
+    }
+
+    /**
+     * Set the flags of the window, as per the
+     * {@link WindowManager.LayoutParams WindowManager.LayoutParams}
+     * flags.
+     *
+     * @param flags The new window flags (see WindowManager.LayoutParams).
+     * @param mask Which of the window flag bits to modify.
+     */
+    public void setPrivateFlags(WindowManager.LayoutParams lp, int flags, int mask) {
+        lp.flags = (lp.flags & ~mask) | (flags & mask);
+    }
+
+    /**
+     * Asserts the top inset of {@param activity} is equal to {@param expected} waiting as needed.
+     */
+    private void assertTopInsetEquals(Activity activity, int expected) throws Exception {
+        waitFor(() -> getInsets(activity).getSystemWindowInsetTop() == expected);
+        assertEquals(expected, getInsets(activity).getSystemWindowInsetTop());
+    }
+
+    /**
+     * Asserts the inset at {@param side} of {@param activity} is equal to {@param expected}
+     * waiting as needed.
+     */
+    private void assertInsetGreaterOrEqual(Activity activity, int side, int expected)
+            throws Exception {
+        waitFor(() -> {
+            final WindowInsets insets = getInsets(activity);
+            switch (side) {
+                case TOP: return insets.getSystemWindowInsetTop() >= expected;
+                case BOTTOM: return insets.getSystemWindowInsetBottom() >= expected;
+                case LEFT: return insets.getSystemWindowInsetLeft() >= expected;
+                case RIGHT: return insets.getSystemWindowInsetRight() >= expected;
+                default: return true;
+            }
+        });
+
+        final WindowInsets insets = getInsets(activity);
+        switch (side) {
+            case TOP: assertGreaterOrEqual(insets.getSystemWindowInsetTop(), expected); break;
+            case BOTTOM: assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), expected); break;
+            case LEFT: assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), expected); break;
+            case RIGHT: assertGreaterOrEqual(insets.getSystemWindowInsetRight(), expected); break;
+        }
+    }
+
+    /** Asserts that the first entry is greater than or equal to the second entry. */
+    private void assertGreaterOrEqual(int first, int second) throws Exception {
+        Assert.assertTrue("Excepted " + first + " >= " + second, first >= second);
+    }
+
+    private void waitFor(BooleanSupplier waitCondition) {
+        int retriesLeft = 5;
+        do {
+            if (waitCondition.getAsBoolean()) {
+                break;
+            }
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                // Well I guess we are not waiting...
+            }
+        } while (retriesLeft-- > 0);
+    }
+
+    private void finishActivity(Activity a) {
+        if (a == null) {
+            return;
+        }
+        a.finish();
+        waitForIdle();
+    }
+
+    private void waitForIdle() {
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private Activity startActivityOnDisplay(Class<?> cls, int displayId) {
+        final Intent intent = new Intent(mContext, cls);
+        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(displayId);
+        final Activity activity = mInstrumentation.startActivitySync(intent, options.toBundle());
+        waitForIdle();
+
+        assertEquals(displayId, activity.getDisplay().getDisplayId());
+        return activity;
+    }
+
+    private Pair<VirtualDisplay, ImageReader> createDisplay() {
+        final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+        final DisplayInfo displayInfo = new DisplayInfo();
+        final Display defaultDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+        defaultDisplay.getDisplayInfo(displayInfo);
+        final String name = "ScreenDecorWindowTests";
+        int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+
+        final ImageReader imageReader = ImageReader.newInstance(
+                displayInfo.logicalWidth, displayInfo.logicalHeight, PixelFormat.RGBA_8888, 2);
+
+        final VirtualDisplay display = dm.createVirtualDisplay(name, displayInfo.logicalWidth,
+                displayInfo.logicalHeight, displayInfo.logicalDensityDpi, imageReader.getSurface(),
+                flags);
+
+        return Pair.create(display, imageReader);
+    }
+
+    public static class TestActivity extends Activity {
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
new file mode 100644
index 0000000..ab0a2bd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 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 android.graphics.Rect;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test class for {@link StackWindowController}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:StackWindowControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class StackWindowControllerTests extends WindowTestsBase {
+    @Test
+    public void testRemoveContainer() throws Exception {
+        final StackWindowController stackController =
+                createStackControllerOnDisplay(mDisplayContent);
+        final WindowTestUtils.TestTaskWindowContainerController taskController =
+                new WindowTestUtils.TestTaskWindowContainerController(stackController);
+
+        final TaskStack stack = stackController.mContainer;
+        final Task task = taskController.mContainer;
+        assertNotNull(stack);
+        assertNotNull(task);
+        stackController.removeContainer();
+        // Assert that the container was removed.
+        assertNull(stackController.mContainer);
+        assertNull(taskController.mContainer);
+        assertNull(stack.getDisplayContent());
+        assertNull(task.getDisplayContent());
+        assertNull(task.mStack);
+    }
+
+    @Test
+    public void testRemoveContainer_deferRemoval() throws Exception {
+        final StackWindowController stackController =
+                createStackControllerOnDisplay(mDisplayContent);
+        final WindowTestUtils.TestTaskWindowContainerController taskController =
+                new WindowTestUtils.TestTaskWindowContainerController(stackController);
+
+        final TaskStack stack = stackController.mContainer;
+        final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer;
+        // Stack removal is deferred if one of its child is animating.
+        task.setLocalIsAnimating(true);
+
+        stackController.removeContainer();
+        // For the case of deferred removal the stack controller will no longer be connected to the
+        // container, but the task controller will still be connected to the its container until
+        // the stack window container is removed.
+        assertNull(stackController.mContainer);
+        assertNull(stack.getController());
+        assertNotNull(taskController.mContainer);
+        assertNotNull(task.getController());
+
+        stack.removeImmediately();
+        assertNull(taskController.mContainer);
+        assertNull(task.getController());
+    }
+
+    @Test
+    public void testReparent() throws Exception {
+        // Create first stack on primary display.
+        final StackWindowController stack1Controller =
+                createStackControllerOnDisplay(mDisplayContent);
+        final TaskStack stack1 = stack1Controller.mContainer;
+        final WindowTestUtils.TestTaskWindowContainerController taskController =
+                new WindowTestUtils.TestTaskWindowContainerController(stack1Controller);
+        final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer;
+        task1.mOnDisplayChangedCalled = false;
+
+        // Create second display and put second stack on it.
+        final DisplayContent dc = createNewDisplay();
+        final StackWindowController stack2Controller =
+                createStackControllerOnDisplay(dc);
+        final TaskStack stack2 = stack2Controller.mContainer;
+
+        // Reparent
+        stack1Controller.reparent(dc.getDisplayId(), new Rect(), true /* onTop */);
+        assertEquals(dc, stack1.getDisplayContent());
+        final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1);
+        final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2);
+        assertEquals(stack1PositionInParent, stack2PositionInParent + 1);
+        assertTrue(task1.mOnDisplayChangedCalled);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
new file mode 100644
index 0000000..edac8a5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2017 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 java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.AnimationHandler;
+import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
+import android.animation.ValueAnimator;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.view.Choreographer;
+import android.view.Choreographer.FrameCallback;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+
+import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
+import com.android.server.wm.SurfaceAnimationRunner.AnimatorFactory;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Test class for {@link SurfaceAnimationRunner}.
+ *
+ * atest FrameworksServicesTests:com.android.server.wm.SurfaceAnimationRunnerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class SurfaceAnimationRunnerTest extends WindowTestsBase {
+
+    @Mock SurfaceControl mMockSurface;
+    @Mock Transaction mMockTransaction;
+    @Mock AnimationSpec mMockAnimationSpec;
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private SurfaceAnimationRunner mSurfaceAnimationRunner;
+    private CountDownLatch mFinishCallbackLatch;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mFinishCallbackLatch = new CountDownLatch(1);
+        mSurfaceAnimationRunner = new SurfaceAnimationRunner(null /* callbackProvider */, null,
+                mMockTransaction);
+    }
+
+    private void finishedCallback() {
+        mFinishCallbackLatch.countDown();
+    }
+
+    @Test
+    public void testAnimation() throws Exception {
+        mSurfaceAnimationRunner
+                .startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
+                this::finishedCallback);
+
+        // Ensure that the initial transformation has been applied.
+        final Matrix m = new Matrix();
+        m.setTranslate(-10, 0);
+        verify(mMockTransaction, atLeastOnce()).setMatrix(eq(mMockSurface), eq(m), any());
+        verify(mMockTransaction, atLeastOnce()).setAlpha(eq(mMockSurface), eq(1.0f));
+
+        mFinishCallbackLatch.await(1, SECONDS);
+        assertFinishCallbackCalled();
+
+        m.setTranslate(10, 0);
+        verify(mMockTransaction, atLeastOnce()).setMatrix(eq(mMockSurface), eq(m), any());
+
+        // At least 3 times: After initialization, first frame, last frame.
+        verify(mMockTransaction, atLeast(3)).setAlpha(eq(mMockSurface), eq(1.0f));
+    }
+
+    @Test
+    public void testCancel_notStarted() throws Exception {
+        mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
+                mMockTransaction);
+        mSurfaceAnimationRunner
+                .startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
+                this::finishedCallback);
+        mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
+        waitUntilHandlersIdle();
+        assertTrue(mSurfaceAnimationRunner.mPendingAnimations.isEmpty());
+        assertFinishCallbackNotCalled();
+    }
+
+    @Test
+    public void testCancel_running() throws Exception {
+        mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
+                mMockTransaction);
+        mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
+                mMockTransaction, this::finishedCallback);
+        waitUntilNextFrame();
+        assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
+        mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
+        assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
+        waitUntilHandlersIdle();
+        assertFinishCallbackNotCalled();
+    }
+
+    @FlakyTest(bugId = 71719744)
+    @Test
+    public void testCancel_sneakyCancelBeforeUpdate() throws Exception {
+        mSurfaceAnimationRunner = new SurfaceAnimationRunner(null, () -> new ValueAnimator() {
+            {
+                setFloatValues(0f, 1f);
+            }
+
+            @Override
+            public void addUpdateListener(AnimatorUpdateListener listener) {
+                super.addUpdateListener(animation -> {
+                    // Sneaky test cancels animation just before applying frame to simulate
+                    // interleaving of multiple threads. Muahahaha
+                    if (animation.getCurrentPlayTime() > 0) {
+                        mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
+                    }
+                    listener.onAnimationUpdate(animation);
+                });
+            }
+        }, mMockTransaction);
+        when(mMockAnimationSpec.getDuration()).thenReturn(200L);
+        mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction,
+                this::finishedCallback);
+
+        // We need to wait for two frames: The first frame starts the animation, the second frame
+        // actually cancels the animation.
+        waitUntilNextFrame();
+        waitUntilNextFrame();
+        assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
+        verify(mMockAnimationSpec, atLeastOnce()).apply(any(), any(), eq(0L));
+    }
+
+    @FlakyTest(bugId = 74780584)
+    @Test
+    public void testDeferStartingAnimations() throws Exception {
+        mSurfaceAnimationRunner.deferStartingAnimations();
+        mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
+                mMockTransaction, this::finishedCallback);
+        waitUntilNextFrame();
+        assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
+        mSurfaceAnimationRunner.continueStartingAnimations();
+        waitUntilNextFrame();
+        assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
+        mFinishCallbackLatch.await(1, SECONDS);
+        assertFinishCallbackCalled();
+    }
+
+    private void waitUntilNextFrame() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mSurfaceAnimationRunner.mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT,
+                latch::countDown, null /* token */);
+        latch.await();
+    }
+
+    private void assertFinishCallbackCalled() {
+        assertEquals(0, mFinishCallbackLatch.getCount());
+    }
+
+    private void assertFinishCallbackNotCalled() {
+        assertEquals(1, mFinishCallbackLatch.getCount());
+    }
+
+    private AnimationSpec createTranslateAnimation() {
+        final Animation a = new TranslateAnimation(-10, 10, 0, 0);
+        a.initialize(0, 0, 0, 0);
+        a.setDuration(50);
+        return new WindowAnimationSpec(a, new Point(0, 0), false /* canSkipFirstFrame */);
+    }
+
+    /**
+     * Callback provider that doesn't animate at all.
+     */
+    private static final class NoOpFrameCallbackProvider implements AnimationFrameCallbackProvider {
+
+        @Override
+        public void postFrameCallback(FrameCallback callback) {
+        }
+
+        @Override
+        public void postCommitCallback(Runnable runnable) {
+        }
+
+        @Override
+        public long getFrameTime() {
+            return 0;
+        }
+
+        @Override
+        public long getFrameDelay() {
+            return 0;
+        }
+
+        @Override
+        public void setFrameDelay(long delay) {
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
new file mode 100644
index 0000000..16b8458
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2017 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Builder;
+import android.view.SurfaceControl.Transaction;
+import android.view.SurfaceSession;
+
+import com.android.server.wm.SurfaceAnimator.Animatable;
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+/**
+ * Test class for {@link SurfaceAnimatorTest}.
+ *
+ * atest FrameworksServicesTests:com.android.server.wm.SurfaceAnimatorTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class SurfaceAnimatorTest extends WindowTestsBase {
+
+    @Mock AnimationAdapter mSpec;
+    @Mock AnimationAdapter mSpec2;
+    @Mock Transaction mTransaction;
+
+    private SurfaceSession mSession = new SurfaceSession();
+    private MyAnimatable mAnimatable;
+    private MyAnimatable mAnimatable2;
+    private DeferFinishAnimatable mDeferFinishAnimatable;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        mAnimatable = new MyAnimatable();
+        mAnimatable2 = new MyAnimatable();
+        mDeferFinishAnimatable = new DeferFinishAnimatable();
+    }
+
+    @Test
+    public void testRunAnimation() throws Exception {
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
+                OnAnimationFinishedCallback.class);
+        assertAnimating(mAnimatable);
+        verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle()));
+        verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
+
+        callbackCaptor.getValue().onAnimationFinished(mSpec);
+        assertNotAnimating(mAnimatable);
+        assertTrue(mAnimatable.mFinishedCallbackCalled);
+        verify(mTransaction).destroy(eq(mAnimatable.mLeash));
+        // TODO: Verify reparenting once we use mPendingTransaction to reparent it back
+    }
+
+    @Test
+    public void testOverrideAnimation() throws Exception {
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        final SurfaceControl firstLeash = mAnimatable.mLeash;
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec2, true /* hidden */);
+
+        verify(mTransaction).destroy(eq(firstLeash));
+        assertFalse(mAnimatable.mFinishedCallbackCalled);
+
+        final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
+                OnAnimationFinishedCallback.class);
+        assertAnimating(mAnimatable);
+        verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
+
+        // First animation was finished, but this shouldn't cancel the second animation
+        callbackCaptor.getValue().onAnimationFinished(mSpec);
+        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
+
+        // Second animation was finished
+        verify(mSpec2).startAnimation(any(), any(), callbackCaptor.capture());
+        callbackCaptor.getValue().onAnimationFinished(mSpec2);
+        assertNotAnimating(mAnimatable);
+        assertTrue(mAnimatable.mFinishedCallbackCalled);
+    }
+
+    @Test
+    public void testCancelAnimation() throws Exception {
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        assertAnimating(mAnimatable);
+        mAnimatable.mSurfaceAnimator.cancelAnimation();
+        assertNotAnimating(mAnimatable);
+        verify(mSpec).onAnimationCancelled(any());
+        assertTrue(mAnimatable.mFinishedCallbackCalled);
+        verify(mTransaction).destroy(eq(mAnimatable.mLeash));
+    }
+
+    @Test
+    public void testDelayingAnimationStart() throws Exception {
+        mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        verifyZeroInteractions(mSpec);
+        assertAnimating(mAnimatable);
+        assertTrue(mAnimatable.mSurfaceAnimator.isAnimationStartDelayed());
+        mAnimatable.mSurfaceAnimator.endDelayingAnimationStart();
+        verify(mSpec).startAnimation(any(), any(), any());
+    }
+
+    @Test
+    public void testDelayingAnimationStartAndCancelled() throws Exception {
+        mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mAnimatable.mSurfaceAnimator.cancelAnimation();
+        verifyZeroInteractions(mSpec);
+        assertNotAnimating(mAnimatable);
+        assertTrue(mAnimatable.mFinishedCallbackCalled);
+        verify(mTransaction).destroy(eq(mAnimatable.mLeash));
+    }
+
+    @Test
+    public void testTransferAnimation() throws Exception {
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+
+        final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
+                OnAnimationFinishedCallback.class);
+        verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
+        final SurfaceControl leash = mAnimatable.mLeash;
+
+        mAnimatable2.mSurfaceAnimator.transferAnimation(mAnimatable.mSurfaceAnimator);
+        assertNotAnimating(mAnimatable);
+        assertAnimating(mAnimatable2);
+        assertEquals(leash, mAnimatable2.mSurfaceAnimator.mLeash);
+        verify(mTransaction, never()).destroy(eq(leash));
+        callbackCaptor.getValue().onAnimationFinished(mSpec);
+        assertNotAnimating(mAnimatable2);
+        assertTrue(mAnimatable2.mFinishedCallbackCalled);
+        verify(mTransaction).destroy(eq(leash));
+    }
+
+    @Test
+    @FlakyTest(detail = "Promote once confirmed non-flaky")
+    public void testDeferFinish() throws Exception {
+
+        // Start animation
+        mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec,
+                true /* hidden */);
+        final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
+                OnAnimationFinishedCallback.class);
+        assertAnimating(mDeferFinishAnimatable);
+        verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
+
+        // Finish the animation but then make sure we are deferring.
+        callbackCaptor.getValue().onAnimationFinished(mSpec);
+        assertAnimating(mDeferFinishAnimatable);
+
+        // Now end defer finishing.
+        mDeferFinishAnimatable.endDeferFinishCallback.run();
+        assertNotAnimating(mAnimatable2);
+        assertTrue(mDeferFinishAnimatable.mFinishedCallbackCalled);
+        verify(mTransaction).destroy(eq(mDeferFinishAnimatable.mLeash));
+    }
+
+    private void assertAnimating(MyAnimatable animatable) {
+        assertTrue(animatable.mSurfaceAnimator.isAnimating());
+        assertNotNull(animatable.mSurfaceAnimator.getAnimation());
+    }
+
+    private void assertNotAnimating(MyAnimatable animatable) {
+        assertFalse(animatable.mSurfaceAnimator.isAnimating());
+        assertNull(animatable.mSurfaceAnimator.getAnimation());
+    }
+
+    private class MyAnimatable implements Animatable {
+
+        final SurfaceControl mParent;
+        final SurfaceControl mSurface;
+        final SurfaceAnimator mSurfaceAnimator;
+        SurfaceControl mLeash;
+        boolean mFinishedCallbackCalled;
+
+        MyAnimatable() {
+            mParent = sWm.makeSurfaceBuilder(mSession)
+                    .setName("test surface parent")
+                    .setSize(3000, 3000)
+                    .build();
+            mSurface = sWm.makeSurfaceBuilder(mSession)
+                    .setName("test surface")
+                    .setSize(1, 1)
+                    .build();
+            mFinishedCallbackCalled = false;
+            mLeash = null;
+            mSurfaceAnimator = new SurfaceAnimator(this, mFinishedCallback, sWm);
+        }
+
+        @Override
+        public Transaction getPendingTransaction() {
+            return mTransaction;
+        }
+
+        @Override
+        public void commitPendingTransaction() {
+        }
+
+        @Override
+        public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
+        }
+
+        @Override
+        public void onAnimationLeashDestroyed(Transaction t) {
+        }
+
+        @Override
+        public Builder makeAnimationLeash() {
+            return new SurfaceControl.Builder(mSession) {
+
+                @Override
+                public SurfaceControl build() {
+                    mLeash = super.build();
+                    return mLeash;
+                }
+            }.setParent(mParent);
+        }
+
+        @Override
+        public SurfaceControl getAnimationLeashParent() {
+            return mParent;
+        }
+
+        @Override
+        public SurfaceControl getSurfaceControl() {
+            return mSurface;
+        }
+
+        @Override
+        public SurfaceControl getParentSurfaceControl() {
+            return mParent;
+        }
+
+        @Override
+        public int getSurfaceWidth() {
+            return 1;
+        }
+
+        @Override
+        public int getSurfaceHeight() {
+            return 1;
+        }
+
+        private final Runnable mFinishedCallback = () -> mFinishedCallbackCalled = true;
+    }
+
+    private class DeferFinishAnimatable extends MyAnimatable {
+
+        Runnable endDeferFinishCallback;
+
+        @Override
+        public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
+            this.endDeferFinishCallback = endDeferFinishCallback;
+            return true;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
new file mode 100644
index 0000000..7bf7dd7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2016 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 org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.graphics.Rect;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+
+import static com.android.server.wm.TaskPositioner.MIN_ASPECT;
+import static com.android.server.wm.WindowManagerService.dipToPixel;
+import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
+import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the {@link TaskPositioner} class.
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskPositionerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TaskPositionerTests extends WindowTestsBase {
+
+    private final boolean DEBUGGING = false;
+    private final String TAG = "TaskPositionerTest";
+
+    private final static int MOUSE_DELTA_X = 5;
+    private final static int MOUSE_DELTA_Y = 5;
+
+    private int mMinVisibleWidth;
+    private int mMinVisibleHeight;
+    private TaskPositioner mPositioner;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        TaskPositioner.setFactory(null);
+
+        final Display display = mDisplayContent.getDisplay();
+        final DisplayMetrics dm = new DisplayMetrics();
+        display.getMetrics(dm);
+
+        // This should be the same calculation as the TaskPositioner uses.
+        mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm);
+        mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
+
+        mPositioner = TaskPositioner.create(sWm);
+        mPositioner.register(mDisplayContent);
+    }
+
+    @Test
+    public void testOverrideFactory() throws Exception {
+        final boolean[] created = new boolean[1];
+        created[0] = false;
+        TaskPositioner.setFactory(new TaskPositioner.Factory() {
+            @Override
+            public TaskPositioner create(WindowManagerService service) {
+                created[0] = true;
+                return null;
+            }
+        });
+
+        assertNull(TaskPositioner.create(sWm));
+        assertTrue(created[0]);
+    }
+
+    /**
+     * This tests that free resizing will allow to change the orientation as well
+     * as does some basic tests (e.g. dragging in Y only will keep X stable).
+     */
+    @Test
+    @Ignore
+    public void testBasicFreeWindowResizing() throws Exception {
+        final Rect r = new Rect(100, 220, 700, 520);
+        final int midY = (r.top + r.bottom) / 2;
+
+        // Start a drag resize starting upper left.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+        assertBoundsEquals(r, mPositioner.getWindowDragBounds());
+
+        // Drag to a good landscape size.
+        mPositioner.resizeDrag(0.0f, 0.0f);
+        assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a good portrait size.
+        mPositioner.resizeDrag(400.0f, 0.0f);
+        assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a too small size for the width.
+        mPositioner.resizeDrag(2000.0f, r.top);
+        assertBoundsEquals(
+                new Rect(r.right - mMinVisibleWidth, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a too small size for the height.
+        mPositioner.resizeDrag(r.left, 2000.0f);
+        assertBoundsEquals(
+                new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Start a drag resize left and see that only the left coord changes..
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r);
+
+        // Drag to the left.
+        mPositioner.resizeDrag(0.0f, midY);
+        assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the right.
+        mPositioner.resizeDrag(200.0f, midY);
+        assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the top
+        mPositioner.resizeDrag(r.left, 0.0f);
+        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the bottom
+        mPositioner.resizeDrag(r.left, 1000.0f);
+        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    /**
+     * This tests that by dragging any edge, the fixed / opposite edge(s) remains anchored.
+     */
+    @Test
+    @Ignore
+    public void testFreeWindowResizingTestAllEdges() throws Exception {
+        final Rect r = new Rect(100, 220, 700, 520);
+        final int midX = (r.left + r.right) / 2;
+        final int midY = (r.top + r.bottom) / 2;
+
+        // Drag upper left.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+        mPositioner.resizeDrag(0.0f, 0.0f);
+        assertTrue(r.left != mPositioner.getWindowDragBounds().left);
+        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+        assertTrue(r.top != mPositioner.getWindowDragBounds().top);
+        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+        // Drag upper.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r);
+        mPositioner.resizeDrag(0.0f, 0.0f);
+        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+        assertTrue(r.top != mPositioner.getWindowDragBounds().top);
+        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+        // Drag upper right.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.right + MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+        mPositioner.resizeDrag(r.right + 100, 0.0f);
+        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+        assertTrue(r.right != mPositioner.getWindowDragBounds().right);
+        assertTrue(r.top != mPositioner.getWindowDragBounds().top);
+        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+        // Drag right.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.right + MOUSE_DELTA_X, midY, r);
+        mPositioner.resizeDrag(r.right + 100, 0.0f);
+        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+        assertTrue(r.right != mPositioner.getWindowDragBounds().right);
+        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+        // Drag bottom right.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/,
+                r.right + MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y, r);
+        mPositioner.resizeDrag(r.right + 100, r.bottom + 100);
+        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+        assertTrue(r.right != mPositioner.getWindowDragBounds().right);
+        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+        assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom);
+
+        // Drag bottom.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, midX, r.bottom + MOUSE_DELTA_Y, r);
+        mPositioner.resizeDrag(r.right + 100, r.bottom + 100);
+        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+        assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom);
+
+        // Drag bottom left.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y, r);
+        mPositioner.resizeDrag(0.0f, r.bottom + 100);
+        assertTrue(r.left != mPositioner.getWindowDragBounds().left);
+        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+        assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom);
+
+        // Drag left.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midX, r);
+        mPositioner.resizeDrag(0.0f, r.bottom + 100);
+        assertTrue(r.left != mPositioner.getWindowDragBounds().left);
+        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+    }
+
+    /**
+     * This tests that a constrained landscape window will keep the aspect and do the
+     * right things upon resizing when dragged from the top left corner.
+     */
+    @Test
+    @Ignore
+    public void testLandscapePreservedWindowResizingDragTopLeft() throws Exception {
+        final Rect r = new Rect(100, 220, 700, 520);
+
+        mPositioner.startDrag(true /*resizing*/,
+                true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+        assertBoundsEquals(r, mPositioner.getWindowDragBounds());
+
+        // Drag to a good landscape size.
+        mPositioner.resizeDrag(0.0f, 0.0f);
+        assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a good portrait size.
+        mPositioner.resizeDrag(400.0f, 0.0f);
+        int width = Math.round((float) (r.bottom - MOUSE_DELTA_Y) * MIN_ASPECT);
+        assertBoundsEquals(new Rect(r.right - width, MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a too small size for the width.
+        mPositioner.resizeDrag(2000.0f, r.top);
+        final int w = mMinVisibleWidth;
+        final int h = Math.round(w / MIN_ASPECT);
+        assertBoundsEquals(new Rect(r.right - w, r.bottom - h, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a too small size for the height.
+        mPositioner.resizeDrag(r.left, 2000.0f);
+        assertBoundsEquals(
+                new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    /**
+     * This tests that a constrained landscape window will keep the aspect and do the
+     * right things upon resizing when dragged from the left corner.
+     */
+    @Test
+    @Ignore
+    public void testLandscapePreservedWindowResizingDragLeft() throws Exception {
+        final Rect r = new Rect(100, 220, 700, 520);
+        final int midY = (r.top + r.bottom) / 2;
+
+        mPositioner.startDrag(true /*resizing*/,
+                true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r);
+
+        // Drag to the left.
+        mPositioner.resizeDrag(0.0f, midY);
+        assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the right.
+        mPositioner.resizeDrag(200.0f, midY);
+        assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag all the way to the right and see the height also shrinking.
+        mPositioner.resizeDrag(2000.0f, midY);
+        final int w = mMinVisibleWidth;
+        final int h = Math.round((float)w / MIN_ASPECT);
+        assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the top.
+        mPositioner.resizeDrag(r.left, 0.0f);
+        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the bottom.
+        mPositioner.resizeDrag(r.left, 1000.0f);
+        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    /**
+     * This tests that a constrained landscape window will keep the aspect and do the
+     * right things upon resizing when dragged from the top corner.
+     */
+    @Test
+    @Ignore
+    public void testLandscapePreservedWindowResizingDragTop() throws Exception {
+        final Rect r = new Rect(100, 220, 700, 520);
+        final int midX = (r.left + r.right) / 2;
+
+        mPositioner.startDrag(true /*resizing*/,
+                true /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r);
+
+        // Drag to the left (no change).
+        mPositioner.resizeDrag(0.0f, r.top);
+        assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the right (no change).
+        mPositioner.resizeDrag(2000.0f, r.top);
+        assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the top.
+        mPositioner.resizeDrag(300.0f, 0.0f);
+        int h = r.bottom - MOUSE_DELTA_Y;
+        int w = Math.max(r.right - r.left, Math.round(h * MIN_ASPECT));
+        assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the bottom.
+        mPositioner.resizeDrag(r.left, 1000.0f);
+        h = mMinVisibleHeight;
+        assertBoundsEquals(new Rect(r.left, r.bottom - h, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    /**
+     * This tests that a constrained portrait window will keep the aspect and do the
+     * right things upon resizing when dragged from the top left corner.
+     */
+    @Test
+    @Ignore
+    public void testPortraitPreservedWindowResizingDragTopLeft() throws Exception {
+        final Rect r = new Rect(330, 100, 630, 600);
+
+        mPositioner.startDrag(true /*resizing*/,
+                true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+        assertBoundsEquals(r, mPositioner.getWindowDragBounds());
+
+        // Drag to a good landscape size.
+        mPositioner.resizeDrag(0.0f, 0.0f);
+        int height = Math.round((float) (r.right - MOUSE_DELTA_X) * MIN_ASPECT);
+        assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.bottom - height, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a good portrait size.
+        mPositioner.resizeDrag(500.0f, 0.0f);
+        assertBoundsEquals(new Rect(500 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a too small size for the height and the the width shrinking.
+        mPositioner.resizeDrag(r.left + MOUSE_DELTA_X, 2000.0f);
+        final int w = Math.max(mMinVisibleWidth, Math.round(mMinVisibleHeight / MIN_ASPECT));
+        final int h = Math.max(mMinVisibleHeight, Math.round(w * MIN_ASPECT));
+        assertBoundsEquals(
+                new Rect(r.right - w, r.bottom - h, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    /**
+     * This tests that a constrained portrait window will keep the aspect and do the
+     * right things upon resizing when dragged from the left corner.
+     */
+    @Test
+    @Ignore
+    public void testPortraitPreservedWindowResizingDragLeft() throws Exception {
+        final Rect r = new Rect(330, 100, 630, 600);
+        final int midY = (r.top + r.bottom) / 2;
+
+        mPositioner.startDrag(true /*resizing*/,
+                true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r);
+
+        // Drag to the left.
+        mPositioner.resizeDrag(0.0f, midY);
+        int w = r.right - MOUSE_DELTA_X;
+        int h = Math.round(w * MIN_ASPECT);
+        assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.top + h),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the right.
+        mPositioner.resizeDrag(450.0f, midY);
+        assertBoundsEquals(new Rect(450 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag all the way to the right.
+        mPositioner.resizeDrag(2000.0f, midY);
+        w = mMinVisibleWidth;
+        h = Math.max(Math.round((float)w * MIN_ASPECT), r.height());
+        assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the top.
+        mPositioner.resizeDrag(r.left, 0.0f);
+        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the bottom.
+        mPositioner.resizeDrag(r.left, 1000.0f);
+        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    /**
+     * This tests that a constrained portrait window will keep the aspect and do the
+     * right things upon resizing when dragged from the top corner.
+     */
+    @Test
+    @Ignore
+    public void testPortraitPreservedWindowResizingDragTop() throws Exception {
+        final Rect r = new Rect(330, 100, 630, 600);
+        final int midX = (r.left + r.right) / 2;
+
+        mPositioner.startDrag(true /*resizing*/,
+                true /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r);
+
+        // Drag to the left (no change).
+        mPositioner.resizeDrag(0.0f, r.top);
+        assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the right (no change).
+        mPositioner.resizeDrag(2000.0f, r.top);
+        assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the top.
+        mPositioner.resizeDrag(300.0f, 0.0f);
+        int h = r.bottom - MOUSE_DELTA_Y;
+        int w = Math.min(r.width(), Math.round(h / MIN_ASPECT));
+        assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the bottom.
+        mPositioner.resizeDrag(r.left, 1000.0f);
+        h = Math.max(mMinVisibleHeight, Math.round(mMinVisibleWidth * MIN_ASPECT));
+        w = Math.round(h / MIN_ASPECT);
+        assertBoundsEquals(new Rect(r.left, r.bottom - h, r.left + w, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    private void assertBoundsEquals(Rect expected, Rect actual) {
+        if (DEBUGGING) {
+            if (!expected.equals(actual)) {
+                Log.e(TAG, "rect(" + actual.toString() + ") != isRect(" + actual.toString()
+                        + ") " + Log.getStackTraceString(new Throwable()));
+            }
+        }
+        assertEquals(expected.left, actual.left);
+        assertEquals(expected.right, actual.right);
+        assertEquals(expected.top, actual.top);
+        assertEquals(expected.bottom, actual.bottom);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskPositioningControllerTests.java
new file mode 100644
index 0000000..6070516
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 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.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.InputChannel;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link TaskPositioningController} class.
+ *
+ * atest com.android.server.wm.TaskPositioningControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class TaskPositioningControllerTests extends WindowTestsBase {
+    private static final int TIMEOUT_MS = 1000;
+    private TaskPositioningController mTarget;
+    private WindowState mWindow;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        assertNotNull(sWm.mTaskPositioningController);
+        mTarget = sWm.mTaskPositioningController;
+
+        when(sWm.mInputManager.transferTouchFocus(
+                any(InputChannel.class),
+                any(InputChannel.class))).thenReturn(true);
+
+        mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
+        mWindow.mInputChannel = new InputChannel();
+        synchronized (sWm.mWindowMap) {
+            sWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
+        }
+    }
+
+    @Test
+    public void testStartAndFinishPositioning() throws Exception {
+        synchronized (sWm.mWindowMap) {
+            assertFalse(mTarget.isPositioningLocked());
+            assertNull(mTarget.getDragWindowHandleLocked());
+        }
+
+        assertTrue(mTarget.startMovingTask(mWindow.mClient, 0, 0));
+
+        synchronized (sWm.mWindowMap) {
+            assertTrue(mTarget.isPositioningLocked());
+            assertNotNull(mTarget.getDragWindowHandleLocked());
+        }
+
+        mTarget.finishTaskPositioning();
+        // Wait until the looper processes finishTaskPositioning.
+        assertTrue(sWm.mH.runWithScissors(() -> {}, TIMEOUT_MS));
+
+        assertFalse(mTarget.isPositioningLocked());
+        assertNull(mTarget.getDragWindowHandleLocked());
+    }
+
+    @Test
+    public void testHandleTapOutsideTask() throws Exception {
+        synchronized (sWm.mWindowMap) {
+
+            assertFalse(mTarget.isPositioningLocked());
+            assertNull(mTarget.getDragWindowHandleLocked());
+        }
+
+        final DisplayContent content = mock(DisplayContent.class);
+        when(content.findTaskForResizePoint(anyInt(), anyInt())).thenReturn(mWindow.getTask());
+        assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow());
+
+        mTarget.handleTapOutsideTask(content, 0, 0);
+        // Wait until the looper processes finishTaskPositioning.
+        assertTrue(sWm.mH.runWithScissors(() -> {}, TIMEOUT_MS));
+
+        synchronized (sWm.mWindowMap) {
+            assertTrue(mTarget.isPositioningLocked());
+            assertNotNull(mTarget.getDragWindowHandleLocked());
+        }
+
+        mTarget.finishTaskPositioning();
+        // Wait until the looper processes finishTaskPositioning.
+        assertTrue(sWm.mH.runWithScissors(() -> {}, TIMEOUT_MS));
+
+        assertFalse(mTarget.isPositioningLocked());
+        assertNull(mTarget.getDragWindowHandleLocked());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
new file mode 100644
index 0000000..649de4a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 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.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskSnapshotCache}.
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskSnapshotCacheTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
+
+    private TaskSnapshotCache mCache;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mCache = new TaskSnapshotCache(sWm, mLoader);
+    }
+
+    @Test
+    public void testAppRemoved() throws Exception {
+        final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+        mCache.putSnapshot(window.getTask(), createSnapshot());
+        assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+                false /* restoreFromDisk */, false /* reducedResolution */));
+        mCache.onAppRemoved(window.mAppToken);
+        assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+                false /* restoreFromDisk */, false /* reducedResolution */));
+    }
+
+    @Test
+    public void testAppDied() throws Exception {
+        final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+        mCache.putSnapshot(window.getTask(), createSnapshot());
+        assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+                false /* restoreFromDisk */, false /* reducedResolution */));
+        mCache.onAppDied(window.mAppToken);
+        assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+                false /* restoreFromDisk */, false /* reducedResolution */));
+    }
+
+    @Test
+    public void testTaskRemoved() throws Exception {
+        final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+        mCache.putSnapshot(window.getTask(), createSnapshot());
+        assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+                false /* restoreFromDisk */, false /* reducedResolution */));
+        mCache.onTaskRemoved(window.getTask().mTaskId);
+        assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+                false /* restoreFromDisk */, false /* reducedResolution */));
+    }
+
+    @Test
+    public void testReduced_notCached() throws Exception {
+        final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+        mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
+        mPersister.waitForQueueEmpty();
+        assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                false /* restoreFromDisk */, false /* reducedResolution */));
+
+        // Load it from disk
+        assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                true /* restoreFromDisk */, true /* reducedResolution */));
+
+        // Make sure it's not in the cache now.
+        assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                false /* restoreFromDisk */, false /* reducedResolution */));
+    }
+
+    @Test
+    public void testRestoreFromDisk() throws Exception {
+        final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+        mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
+        mPersister.waitForQueueEmpty();
+        assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                false /* restoreFromDisk */, false /* reducedResolution */));
+
+        // Load it from disk
+        assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                true /* restoreFromDisk */, false /* reducedResolution */));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java
new file mode 100644
index 0000000..5650050
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 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.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.TRANSIT_UNSET;
+import static com.android.server.wm.TaskSnapshotController.*;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
+
+import com.google.android.collect.Sets;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskSnapshotController}.
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskSnapshotControllerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskSnapshotControllerTest extends WindowTestsBase {
+
+    @Test
+    public void testGetClosingApps_closing() throws Exception {
+        final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
+                "closingWindow");
+        closingWindow.mAppToken.setVisibility(null, false /* visible */, TRANSIT_UNSET,
+                true /* performLayout */, false /* isVoiceInteraction */);
+        final ArraySet<AppWindowToken> closingApps = new ArraySet<>();
+        closingApps.add(closingWindow.mAppToken);
+        final ArraySet<Task> closingTasks = new ArraySet<>();
+        sWm.mTaskSnapshotController.getClosingTasks(closingApps, closingTasks);
+        assertEquals(1, closingTasks.size());
+        assertEquals(closingWindow.mAppToken.getTask(), closingTasks.valueAt(0));
+    }
+
+    @Test
+    public void testGetClosingApps_notClosing() throws Exception {
+        final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
+                "closingWindow");
+        final WindowState openingWindow = createAppWindow(closingWindow.getTask(),
+                FIRST_APPLICATION_WINDOW, "openingWindow");
+        closingWindow.mAppToken.setVisibility(null, false /* visible */, TRANSIT_UNSET,
+                true /* performLayout */, false /* isVoiceInteraction */);
+        openingWindow.mAppToken.setVisibility(null, true /* visible */, TRANSIT_UNSET,
+                true /* performLayout */, false /* isVoiceInteraction */);
+        final ArraySet<AppWindowToken> closingApps = new ArraySet<>();
+        closingApps.add(closingWindow.mAppToken);
+        final ArraySet<Task> closingTasks = new ArraySet<>();
+        sWm.mTaskSnapshotController.getClosingTasks(closingApps, closingTasks);
+        assertEquals(0, closingTasks.size());
+    }
+
+    @Test
+    public void testGetClosingApps_skipClosingAppsSnapshotTasks() throws Exception {
+        final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
+                "closingWindow");
+        closingWindow.mAppToken.setVisibility(null, false /* visible */, TRANSIT_UNSET,
+                true /* performLayout */, false /* isVoiceInteraction */);
+        final ArraySet<AppWindowToken> closingApps = new ArraySet<>();
+        closingApps.add(closingWindow.mAppToken);
+        final ArraySet<Task> closingTasks = new ArraySet<>();
+        sWm.mTaskSnapshotController.addSkipClosingAppSnapshotTasks(
+                Sets.newArraySet(closingWindow.mAppToken.getTask()));
+        sWm.mTaskSnapshotController.getClosingTasks(closingApps, closingTasks);
+        assertEquals(0, closingTasks.size());
+    }
+
+    @Test
+    public void testGetSnapshotMode() throws Exception {
+        final WindowState disabledWindow = createWindow(null,
+                FIRST_APPLICATION_WINDOW, mDisplayContent, "disabledWindow");
+        disabledWindow.mAppToken.setDisablePreviewScreenshots(true);
+        assertEquals(SNAPSHOT_MODE_APP_THEME,
+                sWm.mTaskSnapshotController.getSnapshotMode(disabledWindow.getTask()));
+
+        final WindowState normalWindow = createWindow(null,
+                FIRST_APPLICATION_WINDOW, mDisplayContent, "normalWindow");
+        assertEquals(SNAPSHOT_MODE_REAL,
+                sWm.mTaskSnapshotController.getSnapshotMode(normalWindow.getTask()));
+
+        final WindowState secureWindow = createWindow(null,
+                FIRST_APPLICATION_WINDOW, mDisplayContent, "secureWindow");
+        secureWindow.mAttrs.flags |= FLAG_SECURE;
+        assertEquals(SNAPSHOT_MODE_APP_THEME,
+                sWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask()));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
new file mode 100644
index 0000000..325d42a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2017 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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
+
+import android.view.View;
+import com.android.server.wm.TaskSnapshotPersister.RemoveObsoleteFilesQueueItem;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader}
+ *
+ * atest FrameworksServicesTests:TaskSnapshotPersisterLoaderTest
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase {
+
+    private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
+
+    @Test
+    public void testPersistAndLoadSnapshot() {
+        mPersister.persistSnapshot(1 , mTestUserId, createSnapshot());
+        mPersister.waitForQueueEmpty();
+        final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"),
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg")};
+        assertTrueForFiles(files, File::exists, " must exist");
+        final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, false /* reduced */);
+        assertNotNull(snapshot);
+        assertEquals(TEST_INSETS, snapshot.getContentInsets());
+        assertNotNull(snapshot.getSnapshot());
+        assertEquals(Configuration.ORIENTATION_PORTRAIT, snapshot.getOrientation());
+    }
+
+    private void assertTrueForFiles(File[] files, Predicate<File> predicate, String message) {
+        for (File file : files) {
+            assertTrue(file.getName() + message, predicate.apply(file));
+        }
+    }
+
+    @Test
+    public void testTaskRemovedFromRecents() {
+        mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
+        mPersister.onTaskRemovedFromRecents(1, mTestUserId);
+        mPersister.waitForQueueEmpty();
+        assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.proto").exists());
+        assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.jpg").exists());
+        assertFalse(new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg").exists());
+    }
+
+    /**
+     * Tests that persisting a couple of snapshots is being throttled.
+     */
+    @Test
+    public void testThrottling() {
+        long ms = SystemClock.elapsedRealtime();
+        mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
+        mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
+        mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId });
+        mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId });
+        mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId });
+        mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId });
+        mPersister.waitForQueueEmpty();
+        assertTrue(SystemClock.elapsedRealtime() - ms > 500);
+    }
+
+    /**
+     * Tests that too many store write queue items are being purged.
+     */
+    @Test
+    public void testPurging() {
+        mPersister.persistSnapshot(100, mTestUserId, createSnapshot());
+        mPersister.waitForQueueEmpty();
+        mPersister.setPaused(true);
+        mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
+        mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId });
+        mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
+        mPersister.persistSnapshot(3, mTestUserId, createSnapshot());
+        mPersister.persistSnapshot(4, mTestUserId, createSnapshot());
+        mPersister.setPaused(false);
+        mPersister.waitForQueueEmpty();
+
+        // Make sure 1,2 were purged but removeObsoleteFiles wasn't.
+        final File[] existsFiles = new File[] {
+                new File(sFilesDir.getPath() + "/snapshots/3.proto"),
+                new File(sFilesDir.getPath() + "/snapshots/4.proto")};
+        final File[] nonExistsFiles = new File[] {
+                new File(sFilesDir.getPath() + "/snapshots/100.proto"),
+                new File(sFilesDir.getPath() + "/snapshots/1.proto"),
+                new File(sFilesDir.getPath() + "/snapshots/1.proto")};
+        assertTrueForFiles(existsFiles, File::exists, " must exist");
+        assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
+    }
+
+    @Test
+    public void testGetTaskId() {
+        RemoveObsoleteFilesQueueItem removeObsoleteFilesQueueItem =
+                mPersister.new RemoveObsoleteFilesQueueItem(new ArraySet<>(), new int[] {});
+        assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp"));
+        assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err"));
+        assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/"));
+        assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.jpg"));
+        assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.proto"));
+        assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.jpg"));
+        assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1_reduced.jpg"));
+    }
+
+    @Test
+    public void testLowResolutionPersistAndLoadSnapshot() {
+        TaskSnapshot a = createSnapshot(0.5f /* reducedResolution */);
+        assertTrue(a.isReducedResolution());
+        mPersister.persistSnapshot(1 , mTestUserId, a);
+        mPersister.waitForQueueEmpty();
+        final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg")};
+        final File[] nonExistsFiles = new File[] {
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+        };
+        assertTrueForFiles(files, File::exists, " must exist");
+        assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
+        final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, true /* reduced */);
+        assertNotNull(snapshot);
+        assertEquals(TEST_INSETS, snapshot.getContentInsets());
+        assertNotNull(snapshot.getSnapshot());
+        assertEquals(Configuration.ORIENTATION_PORTRAIT, snapshot.getOrientation());
+
+        final TaskSnapshot snapshotNotExist = mLoader.loadTask(1, mTestUserId, false /* reduced */);
+        assertNull(snapshotNotExist);
+    }
+
+    @Test
+    public void testIsRealSnapshotPersistAndLoadSnapshot() {
+        TaskSnapshot a = new TaskSnapshotBuilder()
+                .setIsRealSnapshot(true)
+                .build();
+        TaskSnapshot b = new TaskSnapshotBuilder()
+                .setIsRealSnapshot(false)
+                .build();
+        assertTrue(a.isRealSnapshot());
+        assertFalse(b.isRealSnapshot());
+        mPersister.persistSnapshot(1, mTestUserId, a);
+        mPersister.persistSnapshot(2, mTestUserId, b);
+        mPersister.waitForQueueEmpty();
+        final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */);
+        final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */);
+        assertNotNull(snapshotA);
+        assertNotNull(snapshotB);
+        assertTrue(snapshotA.isRealSnapshot());
+        assertFalse(snapshotB.isRealSnapshot());
+    }
+
+    @Test
+    public void testWindowingModePersistAndLoadSnapshot() {
+        TaskSnapshot a = new TaskSnapshotBuilder()
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .build();
+        TaskSnapshot b = new TaskSnapshotBuilder()
+                .setWindowingMode(WINDOWING_MODE_PINNED)
+                .build();
+        assertTrue(a.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
+        assertTrue(b.getWindowingMode() == WINDOWING_MODE_PINNED);
+        mPersister.persistSnapshot(1, mTestUserId, a);
+        mPersister.persistSnapshot(2, mTestUserId, b);
+        mPersister.waitForQueueEmpty();
+        final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */);
+        final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */);
+        assertNotNull(snapshotA);
+        assertNotNull(snapshotB);
+        assertTrue(snapshotA.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
+        assertTrue(snapshotB.getWindowingMode() == WINDOWING_MODE_PINNED);
+    }
+
+    @Test
+    public void testIsTranslucentPersistAndLoadSnapshot() {
+        TaskSnapshot a = new TaskSnapshotBuilder()
+                .setIsTranslucent(true)
+                .build();
+        TaskSnapshot b = new TaskSnapshotBuilder()
+                .setIsTranslucent(false)
+                .build();
+        assertTrue(a.isTranslucent());
+        assertFalse(b.isTranslucent());
+        mPersister.persistSnapshot(1, mTestUserId, a);
+        mPersister.persistSnapshot(2, mTestUserId, b);
+        mPersister.waitForQueueEmpty();
+        final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */);
+        final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */);
+        assertNotNull(snapshotA);
+        assertNotNull(snapshotB);
+        assertTrue(snapshotA.isTranslucent());
+        assertFalse(snapshotB.isTranslucent());
+    }
+
+    @Test
+    public void testSystemUiVisibilityPersistAndLoadSnapshot() {
+        final int lightBarFlags = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+                | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+        TaskSnapshot a = new TaskSnapshotBuilder()
+                .setSystemUiVisibility(0)
+                .build();
+        TaskSnapshot b = new TaskSnapshotBuilder()
+                .setSystemUiVisibility(lightBarFlags)
+                .build();
+        assertTrue(a.getSystemUiVisibility() == 0);
+        assertTrue(b.getSystemUiVisibility() == lightBarFlags);
+        mPersister.persistSnapshot(1, mTestUserId, a);
+        mPersister.persistSnapshot(2, mTestUserId, b);
+        mPersister.waitForQueueEmpty();
+        final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */);
+        final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */);
+        assertNotNull(snapshotA);
+        assertNotNull(snapshotB);
+        assertTrue(snapshotA.getSystemUiVisibility() == 0);
+        assertTrue(snapshotB.getSystemUiVisibility() == lightBarFlags);
+    }
+
+    @Test
+    public void testRemoveObsoleteFiles() {
+        mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
+        mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
+        final ArraySet<Integer> taskIds = new ArraySet<>();
+        taskIds.add(1);
+        mPersister.removeObsoleteFiles(taskIds, new int[] { mTestUserId });
+        mPersister.waitForQueueEmpty();
+        final File[] existsFiles = new File[] {
+                new File(sFilesDir.getPath() + "/snapshots/1.proto"),
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg") };
+        final File[] nonExistsFiles = new File[] {
+                new File(sFilesDir.getPath() + "/snapshots/2.proto"),
+                new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
+        assertTrueForFiles(existsFiles, File::exists, " must exist");
+        assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
+    }
+
+    @Test
+    public void testRemoveObsoleteFiles_addedOneInTheMeantime() {
+        mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
+        final ArraySet<Integer> taskIds = new ArraySet<>();
+        taskIds.add(1);
+        mPersister.removeObsoleteFiles(taskIds, new int[] { mTestUserId });
+        mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
+        mPersister.waitForQueueEmpty();
+        final File[] existsFiles = new File[] {
+                new File(sFilesDir.getPath() + "/snapshots/1.proto"),
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/2.proto"),
+                new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
+        assertTrueForFiles(existsFiles, File::exists, " must exist");
+    }
+
+    /**
+     * Private predicate definition.
+     *
+     * This is needed because com.android.internal.util.Predicate is deprecated
+     * and can only be used with classes fron android.test.runner. This cannot
+     * use java.util.function.Predicate because that is not present on all API
+     * versions that this test must run on.
+     */
+    private interface Predicate<T> {
+        boolean apply(T t);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
new file mode 100644
index 0000000..8b86043
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017 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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE;
+import static android.graphics.GraphicBuffer.USAGE_SW_READ_RARELY;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+import java.io.File;
+
+/**
+ * Base class for tests that use a {@link TaskSnapshotPersister}.
+ */
+class TaskSnapshotPersisterTestBase extends WindowTestsBase {
+
+    private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
+
+    TaskSnapshotPersister mPersister;
+    TaskSnapshotLoader mLoader;
+    int mTestUserId;
+    static File sFilesDir;
+
+    @BeforeClass
+    public static void setUpUser() {
+        sFilesDir = InstrumentationRegistry.getContext().getFilesDir();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        final UserManager um = UserManager.get(InstrumentationRegistry.getContext());
+        mTestUserId = um.getUserHandle();
+        mPersister = new TaskSnapshotPersister(userId -> sFilesDir);
+        mLoader = new TaskSnapshotLoader(mPersister);
+        mPersister.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        cleanDirectory();
+    }
+
+    private void cleanDirectory() {
+        final File[] files = new File(sFilesDir, "snapshots").listFiles();
+        if (files == null) {
+            return;
+        }
+        for (File file : files) {
+            if (!file.isDirectory()) {
+                file.delete();
+            }
+        }
+    }
+
+    TaskSnapshot createSnapshot() {
+        return createSnapshot(1f /* scale */);
+    }
+
+    TaskSnapshot createSnapshot(float scale) {
+        return new TaskSnapshotBuilder()
+                .setScale(scale)
+                .build();
+    }
+
+    /**
+     * Builds a TaskSnapshot.
+     */
+    class TaskSnapshotBuilder {
+
+        private float mScale = 1f;
+        private boolean mIsRealSnapshot = true;
+        private boolean mIsTranslucent = false;
+        private int mWindowingMode = WINDOWING_MODE_FULLSCREEN;
+        private int mSystemUiVisibility = 0;
+
+        public TaskSnapshotBuilder setScale(float scale) {
+            mScale = scale;
+            return this;
+        }
+
+        public TaskSnapshotBuilder setIsRealSnapshot(boolean isRealSnapshot) {
+            mIsRealSnapshot = isRealSnapshot;
+            return this;
+        }
+
+        public TaskSnapshotBuilder setIsTranslucent(boolean isTranslucent) {
+            mIsTranslucent = isTranslucent;
+            return this;
+        }
+
+        public TaskSnapshotBuilder setWindowingMode(int windowingMode) {
+            mWindowingMode = windowingMode;
+            return this;
+        }
+
+        public TaskSnapshotBuilder setSystemUiVisibility(int systemUiVisibility) {
+            mSystemUiVisibility = systemUiVisibility;
+            return this;
+        }
+
+        public TaskSnapshot build() {
+            final GraphicBuffer buffer = GraphicBuffer.create(100, 100, PixelFormat.RGBA_8888,
+                    USAGE_HW_TEXTURE | USAGE_SW_READ_RARELY | USAGE_SW_READ_RARELY);
+            Canvas c = buffer.lockCanvas();
+            c.drawColor(Color.RED);
+            buffer.unlockCanvasAndPost(c);
+            return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS,
+                    mScale < 1f /* reducedResolution */, mScale, mIsRealSnapshot, mWindowingMode,
+                    mSystemUiVisibility, mIsTranslucent);
+        }
+
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
new file mode 100644
index 0000000..b19373e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2017 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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Surface;
+
+import com.android.server.wm.TaskSnapshotSurface.Window;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskSnapshotSurface}.
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskSnapshotSurfaceTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskSnapshotSurfaceTest extends WindowTestsBase {
+
+    private TaskSnapshotSurface mSurface;
+
+    private void setupSurface(int width, int height, Rect contentInsets, int sysuiVis,
+            int windowFlags, Rect taskBounds) {
+        final GraphicBuffer buffer = GraphicBuffer.create(width, height, PixelFormat.RGBA_8888,
+                GraphicBuffer.USAGE_SW_READ_NEVER | GraphicBuffer.USAGE_SW_WRITE_NEVER);
+        final TaskSnapshot snapshot = new TaskSnapshot(buffer,
+                ORIENTATION_PORTRAIT, contentInsets, false, 1.0f, true /* isRealSnapshot */,
+                WINDOWING_MODE_FULLSCREEN, 0 /* systemUiVisibility */, false /* isTranslucent */);
+        mSurface = new TaskSnapshotSurface(sWm, new Window(), new Surface(), snapshot, "Test",
+                Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds,
+                ORIENTATION_PORTRAIT);
+    }
+
+    private void setupSurface(int width, int height) {
+        setupSurface(width, height, new Rect(), 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                new Rect(0, 0, width, height));
+    }
+
+    @Test
+    public void fillEmptyBackground_fillHorizontally() throws Exception {
+        setupSurface(200, 100);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(200);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200));
+        verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
+    }
+
+    @Test
+    public void fillEmptyBackground_fillVertically() throws Exception {
+        setupSurface(100, 200);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(200);
+        mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100));
+        verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any());
+    }
+
+    @Test
+    public void fillEmptyBackground_fillBoth() throws Exception {
+        setupSurface(200, 200);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(200);
+        when(mockCanvas.getHeight()).thenReturn(200);
+        mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
+        verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
+        verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any());
+    }
+
+    @Test
+    public void fillEmptyBackground_dontFill_sameSize() throws Exception {
+        setupSurface(100, 100);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
+        verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+    }
+
+    @Test
+    public void fillEmptyBackground_dontFill_bitmapLarger() throws Exception {
+        setupSurface(100, 100);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200));
+        verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+    }
+
+    @Test
+    public void testCalculateSnapshotCrop() {
+        setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 0, 100, 100));
+        assertEquals(new Rect(0, 0, 100, 90), mSurface.calculateSnapshotCrop());
+    }
+
+    @Test
+    public void testCalculateSnapshotCrop_taskNotOnTop() {
+        setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 50, 100, 100));
+        assertEquals(new Rect(0, 10, 100, 90), mSurface.calculateSnapshotCrop());
+    }
+
+    @Test
+    public void testCalculateSnapshotCrop_navBarLeft() {
+        setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, 0, new Rect(0, 0, 100, 100));
+        assertEquals(new Rect(10, 0, 100, 100), mSurface.calculateSnapshotCrop());
+    }
+
+    @Test
+    public void testCalculateSnapshotCrop_navBarRight() {
+        setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, 0, new Rect(0, 0, 100, 100));
+        assertEquals(new Rect(0, 0, 90, 100), mSurface.calculateSnapshotCrop());
+    }
+
+    @Test
+    public void testCalculateSnapshotFrame() {
+        setupSurface(100, 100);
+        final Rect insets = new Rect(0, 10, 0, 10);
+        mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets);
+        assertEquals(new Rect(0, -10, 100, 70),
+                mSurface.calculateSnapshotFrame(new Rect(0, 10, 100, 90)));
+    }
+
+    @Test
+    public void testCalculateSnapshotFrame_navBarLeft() {
+        setupSurface(100, 100);
+        final Rect insets = new Rect(10, 10, 0, 0);
+        mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets);
+        assertEquals(new Rect(0, -10, 90, 80),
+                mSurface.calculateSnapshotFrame(new Rect(10, 10, 100, 100)));
+    }
+
+    @Test
+    public void testDrawStatusBarBackground() {
+        setupSurface(100, 100);
+        final Rect insets = new Rect(0, 10, 10, 0);
+        mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSurface.mSystemBarBackgroundPainter.drawStatusBarBackground(
+                mockCanvas, new Rect(0, 0, 50, 100), 10);
+        verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any());
+    }
+
+    @Test
+    public void testDrawStatusBarBackground_nullFrame() {
+        setupSurface(100, 100);
+        final Rect insets = new Rect(0, 10, 10, 0);
+        mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSurface.mSystemBarBackgroundPainter.drawStatusBarBackground(
+                mockCanvas, null, 10);
+        verify(mockCanvas).drawRect(eq(0.0f), eq(0.0f), eq(90.0f), eq(10.0f), any());
+    }
+
+    @Test
+    public void testDrawStatusBarBackground_nope() {
+        setupSurface(100, 100);
+        final Rect insets = new Rect(0, 10, 10, 0);
+        mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSurface.mSystemBarBackgroundPainter.drawStatusBarBackground(
+                mockCanvas, new Rect(0, 0, 100, 100), 10);
+        verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+    }
+
+    @Test
+    public void testDrawNavigationBarBackground() {
+        final Rect insets = new Rect(0, 10, 0, 10);
+        setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                new Rect(0, 0, 100, 100));
+        mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSurface.mSystemBarBackgroundPainter.drawNavigationBarBackground(mockCanvas);
+        verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any());
+    }
+
+    @Test
+    public void testDrawNavigationBarBackground_left() {
+        final Rect insets = new Rect(10, 10, 0, 0);
+        setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                new Rect(0, 0, 100, 100));
+        mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSurface.mSystemBarBackgroundPainter.drawNavigationBarBackground(mockCanvas);
+        verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any());
+    }
+
+    @Test
+    public void testDrawNavigationBarBackground_right() {
+        final Rect insets = new Rect(0, 10, 10, 0);
+        setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                new Rect(0, 0, 100, 100));
+        mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSurface.mSystemBarBackgroundPainter.drawNavigationBarBackground(mockCanvas);
+        verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
new file mode 100644
index 0000000..ca1994f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 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.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.After;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+/**
+ * Tests for the {@link DisplayContent.TaskStackContainers} container in {@link DisplayContent}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.TaskStackContainersTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskStackContainersTests extends WindowTestsBase {
+
+    private TaskStack mPinnedStack;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mPinnedStack = createStackControllerOnStackOnDisplay(
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer;
+        // Stack should contain visible app window to be considered visible.
+        final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */);
+        assertFalse(mPinnedStack.isVisible());
+        final WindowTestUtils.TestAppWindowToken pinnedApp =
+                WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+        pinnedTask.addChild(pinnedApp, 0 /* addPos */);
+        assertTrue(mPinnedStack.isVisible());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mPinnedStack.removeImmediately();
+    }
+
+    @Test
+    public void testStackPositionChildAt() throws Exception {
+        // Test that always-on-top stack can't be moved to position other than top.
+        final TaskStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+        final TaskStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+
+        final WindowContainer taskStackContainer = stack1.getParent();
+
+        final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1);
+        final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2);
+        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
+        assertGreaterThan(pinnedStackPos, stack2Pos);
+        assertGreaterThan(stack2Pos, stack1Pos);
+
+        taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedStack, false);
+        assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
+        assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+
+        taskStackContainer.positionChildAt(1, mPinnedStack, false);
+        assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
+        assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+    }
+
+    @Test
+    public void testStackPositionBelowPinnedStack() throws Exception {
+        // Test that no stack can be above pinned stack.
+        final TaskStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+
+        final WindowContainer taskStackContainer = stack1.getParent();
+
+        final int stackPos = taskStackContainer.mChildren.indexOf(stack1);
+        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
+        assertGreaterThan(pinnedStackPos, stackPos);
+
+        taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false);
+        assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+
+        taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false);
+        assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
new file mode 100644
index 0000000..eaf71f0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 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 org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the {@link TaskStack} class.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.TaskStackTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskStackTests extends WindowTestsBase {
+
+    @Test
+    public void testStackPositionChildAt() throws Exception {
+        final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task1 = createTaskInStack(stack, 0 /* userId */);
+        final Task task2 = createTaskInStack(stack, 1 /* userId */);
+
+        // Current user task should be moved to top.
+        stack.positionChildAt(WindowContainer.POSITION_TOP, task1, false /* includingParents */);
+        assertEquals(stack.mChildren.get(0), task2);
+        assertEquals(stack.mChildren.get(1), task1);
+
+        // Non-current user won't be moved to top.
+        stack.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */);
+        assertEquals(stack.mChildren.get(0), task2);
+        assertEquals(stack.mChildren.get(1), task1);
+    }
+
+    @Test
+    public void testClosingAppDifferentStackOrientation() throws Exception {
+        final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task1 = createTaskInStack(stack, 0 /* userId */);
+        WindowTestUtils.TestAppWindowToken appWindowToken1 =
+                WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+        task1.addChild(appWindowToken1, 0);
+        appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+        final Task task2 = createTaskInStack(stack, 1 /* userId */);
+        WindowTestUtils.TestAppWindowToken appWindowToken2 =
+                WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+        task2.addChild(appWindowToken2, 0);
+        appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
+
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation());
+        sWm.mClosingApps.add(appWindowToken2);
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, stack.getOrientation());
+    }
+
+    @Test
+    public void testMoveTaskToBackDifferentStackOrientation() throws Exception {
+        final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task1 = createTaskInStack(stack, 0 /* userId */);
+        WindowTestUtils.TestAppWindowToken appWindowToken1 =
+                WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+        task1.addChild(appWindowToken1, 0);
+        appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+        final Task task2 = createTaskInStack(stack, 1 /* userId */);
+        WindowTestUtils.TestAppWindowToken appWindowToken2 =
+                WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+        task2.addChild(appWindowToken2, 0);
+        appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
+
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation());
+        task2.setSendingToBottom(true);
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, stack.getOrientation());
+    }
+
+    @Test
+    public void testStackRemoveImmediately() throws Exception {
+        final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        assertEquals(stack, task.mStack);
+
+        // Remove stack and check if its child is also removed.
+        stack.removeImmediately();
+        assertNull(stack.getDisplayContent());
+        assertNull(task.mStack);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
new file mode 100644
index 0000000..1dd9365
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2017 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 android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test class for {@link TaskWindowContainerController}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.TaskWindowContainerControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskWindowContainerControllerTests extends WindowTestsBase {
+
+    @Test
+    public void testRemoveContainer() throws Exception {
+        final WindowTestUtils.TestTaskWindowContainerController taskController =
+                new WindowTestUtils.TestTaskWindowContainerController(this);
+        final WindowTestUtils.TestAppWindowContainerController appController =
+                new WindowTestUtils.TestAppWindowContainerController(taskController);
+
+        taskController.removeContainer();
+        // Assert that the container was removed.
+        assertNull(taskController.mContainer);
+        assertNull(appController.mContainer);
+    }
+
+    @Test
+    public void testRemoveContainer_deferRemoval() throws Exception {
+        final WindowTestUtils.TestTaskWindowContainerController taskController =
+                new WindowTestUtils.TestTaskWindowContainerController(this);
+        final WindowTestUtils.TestAppWindowContainerController appController =
+                new WindowTestUtils.TestAppWindowContainerController(taskController);
+
+        final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer;
+        final AppWindowToken app = appController.mContainer;
+        task.mShouldDeferRemoval = true;
+
+        taskController.removeContainer();
+        // For the case of deferred removal the task controller will no longer be connected to the
+        // container, but the app controller will still be connected to the its container until
+        // the task window container is removed.
+        assertNull(taskController.mContainer);
+        assertNull(task.getController());
+        assertNotNull(appController.mContainer);
+        assertNotNull(app.getController());
+
+        task.removeImmediately();
+        assertNull(appController.mContainer);
+        assertNull(app.getController());
+    }
+
+    @Test
+    public void testReparent() throws Exception {
+        final StackWindowController stackController1 =
+                createStackControllerOnDisplay(mDisplayContent);
+        final WindowTestUtils.TestTaskWindowContainerController taskController =
+                new WindowTestUtils.TestTaskWindowContainerController(stackController1);
+        final StackWindowController stackController2 =
+                createStackControllerOnDisplay(mDisplayContent);
+        final WindowTestUtils.TestTaskWindowContainerController taskController2 =
+                new WindowTestUtils.TestTaskWindowContainerController(stackController2);
+
+        boolean gotException = false;
+        try {
+            taskController.reparent(stackController1, 0, false/* moveParents */);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue("Should not be able to reparent to the same parent", gotException);
+
+        final StackWindowController stackController3 =
+                createStackControllerOnDisplay(mDisplayContent);
+        stackController3.setContainer(null);
+        gotException = false;
+        try {
+            taskController.reparent(stackController3, 0, false/* moveParents */);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue("Should not be able to reparent to a stack that doesn't have a container",
+                gotException);
+
+        taskController.reparent(stackController2, 0, false/* moveParents */);
+        assertEquals(stackController2.mContainer, taskController.mContainer.getParent());
+        assertEquals(0, ((WindowTestUtils.TestTask) taskController.mContainer).positionInParent());
+        assertEquals(1, ((WindowTestUtils.TestTask) taskController2.mContainer).positionInParent());
+    }
+
+    @Test
+    public void testReparent_BetweenDisplays() throws Exception {
+        // Create first stack on primary display.
+        final StackWindowController stack1Controller =
+                createStackControllerOnDisplay(mDisplayContent);
+        final TaskStack stack1 = stack1Controller.mContainer;
+        final WindowTestUtils.TestTaskWindowContainerController taskController =
+                new WindowTestUtils.TestTaskWindowContainerController(stack1Controller);
+        final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer;
+        task1.mOnDisplayChangedCalled = false;
+        assertEquals(mDisplayContent, stack1.getDisplayContent());
+
+        // Create second display and put second stack on it.
+        final DisplayContent dc = createNewDisplay();
+        final StackWindowController stack2Controller = createStackControllerOnDisplay(dc);
+        final TaskStack stack2 = stack2Controller.mContainer;
+        final WindowTestUtils.TestTaskWindowContainerController taskController2 =
+                new WindowTestUtils.TestTaskWindowContainerController(stack2Controller);
+        final WindowTestUtils.TestTask task2 =
+                (WindowTestUtils.TestTask) taskController2.mContainer;
+
+        // Reparent and check state
+        taskController.reparent(stack2Controller, 0, false /* moveParents */);
+        assertEquals(stack2, task1.getParent());
+        assertEquals(0, task1.positionInParent());
+        assertEquals(1, task2.positionInParent());
+        assertTrue(task1.mOnDisplayChangedCalled);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
new file mode 100644
index 0000000..353aa7d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 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 com.android.internal.os.IResultReceiver;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.MergedConfiguration;
+import android.view.DisplayCutout;
+import android.view.DragEvent;
+import android.view.IWindow;
+
+public class TestIWindow extends IWindow.Stub {
+    @Override
+    public void executeCommand(String command, String parameters,
+            ParcelFileDescriptor descriptor)
+            throws RemoteException {
+
+    }
+
+    @Override
+    public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets,
+            Rect stableInsets, Rect outsets, boolean reportDraw, MergedConfiguration mergedConfig,
+            Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId,
+            DisplayCutout.ParcelableWrapper displayCutout)
+            throws RemoteException {
+
+    }
+
+    @Override
+    public void moved(int newX, int newY) throws RemoteException {
+
+    }
+
+    @Override
+    public void dispatchAppVisibility(boolean visible) throws RemoteException {
+
+    }
+
+    @Override
+    public void dispatchGetNewSurface() throws RemoteException {
+
+    }
+
+    @Override
+    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode)
+            throws RemoteException {
+
+    }
+
+    @Override
+    public void closeSystemDialogs(String reason) throws RemoteException {
+
+    }
+
+    @Override
+    public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
+            boolean sync)
+            throws RemoteException {
+
+    }
+
+    @Override
+    public void dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras,
+            boolean sync) throws RemoteException {
+
+    }
+
+    @Override
+    public void dispatchDragEvent(DragEvent event) throws RemoteException {
+
+    }
+
+    @Override
+    public void updatePointerIcon(float x, float y) throws RemoteException {
+
+    }
+
+    @Override
+    public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, int localValue,
+            int localChanges) throws RemoteException {
+
+    }
+
+    @Override
+    public void dispatchWindowShown() throws RemoteException {
+
+    }
+
+    @Override
+    public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId)
+            throws RemoteException {
+
+    }
+
+    @Override
+    public void dispatchPointerCaptureChanged(boolean hasCapture) {
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
new file mode 100644
index 0000000..ee028ba
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -0,0 +1,606 @@
+/*
+ * Copyright (C) 2016 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.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+
+import static org.mockito.Mockito.mock;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.IWindow;
+import android.view.IWindowManager;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+
+import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.policy.IShortcutService;
+import com.android.server.policy.WindowManagerPolicy;
+
+import java.io.PrintWriter;
+import java.util.function.Supplier;
+
+class TestWindowManagerPolicy implements WindowManagerPolicy {
+    private static final String TAG = "TestWindowManagerPolicy";
+
+    private final Supplier<WindowManagerService> mWmSupplier;
+
+    int rotationToReport = 0;
+    boolean keyguardShowingAndNotOccluded = false;
+
+    private Runnable mRunnableWhenAddingSplashScreen;
+
+    public TestWindowManagerPolicy(Supplier<WindowManagerService> wmSupplier) {
+
+        mWmSupplier = wmSupplier;
+    }
+
+    @Override
+    public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+            throws RemoteException {
+
+    }
+
+    @Override
+    public void init(Context context, IWindowManager windowManager,
+            WindowManagerFuncs windowManagerFuncs) {
+
+    }
+
+    @Override
+    public boolean isDefaultOrientationForced() {
+        return false;
+    }
+
+    @Override
+    public void setInitialDisplaySize(Display display, int width, int height, int density) {
+
+    }
+
+    @Override
+    public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
+        return 0;
+    }
+
+    @Override
+    public boolean checkShowToOwnerOnly(WindowManager.LayoutParams attrs) {
+        return false;
+    }
+
+    @Override
+    public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs,
+            boolean hasStatusBarServicePermission) {
+    }
+
+    @Override
+    public void adjustConfigurationLw(Configuration config, int keyboardPresence,
+            int navigationPresence) {
+
+    }
+
+    @Override
+    public int getMaxWallpaperLayer() {
+        return 0;
+    }
+
+    @Override
+    public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
+            int displayId, DisplayCutout displayCutout) {
+        return 0;
+    }
+
+    @Override
+    public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode,
+            int displayId, DisplayCutout displayCutout) {
+        return 0;
+    }
+
+    @Override
+    public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
+            int displayId, DisplayCutout displayCutout) {
+        return 0;
+    }
+
+    @Override
+    public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode,
+            int displayId, DisplayCutout displayCutout) {
+        return 0;
+    }
+
+    @Override
+    public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs) {
+        return attrs.type == TYPE_STATUS_BAR;
+    }
+
+    @Override
+    public boolean canBeHiddenByKeyguardLw(WindowState win) {
+        return false;
+    }
+
+    /**
+     * Sets a runnable to run when adding a splash screen which gets executed after the window has
+     * been added but before returning the surface.
+     */
+    void setRunnableWhenAddingSplashScreen(Runnable r) {
+        mRunnableWhenAddingSplashScreen = r;
+    }
+
+    @Override
+    public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
+            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
+            int logo, int windowFlags, Configuration overrideConfig, int displayId) {
+        final com.android.server.wm.WindowState window;
+        final AppWindowToken atoken;
+        final WindowManagerService wm = mWmSupplier.get();
+        synchronized (wm.mWindowMap) {
+            atoken = wm.mRoot.getAppWindowToken(appToken);
+            window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, atoken,
+                    "Starting window", 0 /* ownerId */, false /* internalWindows */, wm,
+                    mock(Session.class), mock(IWindow.class));
+            atoken.startingWindow = window;
+        }
+        if (mRunnableWhenAddingSplashScreen != null) {
+            mRunnableWhenAddingSplashScreen.run();
+            mRunnableWhenAddingSplashScreen = null;
+        }
+        return () -> {
+            synchronized (wm.mWindowMap) {
+                atoken.removeChild(window);
+                atoken.startingWindow = null;
+            }
+        };
+    }
+
+    @Override
+    public int prepareAddWindowLw(WindowState win,
+            WindowManager.LayoutParams attrs) {
+        return 0;
+    }
+
+    @Override
+    public void removeWindowLw(WindowState win) {
+
+    }
+
+    @Override
+    public int selectAnimationLw(WindowState win, int transit) {
+        return 0;
+    }
+
+    @Override
+    public void selectRotationAnimationLw(int[] anim) {
+
+    }
+
+    @Override
+    public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId,
+            boolean forceDefault) {
+        return false;
+    }
+
+    @Override
+    public Animation createHiddenByKeyguardExit(boolean onWallpaper,
+            boolean goingToNotificationShade) {
+        return null;
+    }
+
+    @Override
+    public Animation createKeyguardWallpaperExit(boolean goingToNotificationShade) {
+        return null;
+    }
+
+    @Override
+    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
+        return 0;
+    }
+
+    @Override
+    public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) {
+        return 0;
+    }
+
+    @Override
+    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event,
+            int policyFlags) {
+        return 0;
+    }
+
+    @Override
+    public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event,
+            int policyFlags) {
+        return null;
+    }
+
+    @Override
+    public int getSystemDecorLayerLw() {
+        return 0;
+    }
+
+    @Override
+    public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight) {
+
+    }
+
+    @Override
+    public void applyPostLayoutPolicyLw(WindowState win,
+            WindowManager.LayoutParams attrs, WindowState attached, WindowState imeTarget) {
+    }
+
+    @Override
+    public int finishPostLayoutPolicyLw() {
+        return 0;
+    }
+
+    @Override
+    public boolean allowAppAnimationsLw() {
+        return false;
+    }
+
+    @Override
+    public int focusChangedLw(WindowState lastFocus,
+            WindowState newFocus) {
+        return 0;
+    }
+
+    @Override
+    public void startedWakingUp() {
+
+    }
+
+    @Override
+    public void finishedWakingUp() {
+
+    }
+
+    @Override
+    public void startedGoingToSleep(int why) {
+
+    }
+
+    @Override
+    public void finishedGoingToSleep(int why) {
+
+    }
+
+    @Override
+    public void screenTurningOn(ScreenOnListener screenOnListener) {
+
+    }
+
+    @Override
+    public void screenTurnedOn() {
+
+    }
+
+    @Override
+    public void screenTurningOff(ScreenOffListener screenOffListener) {
+
+    }
+
+    @Override
+    public void screenTurnedOff() {
+
+    }
+
+    @Override
+    public boolean isScreenOn() {
+        return true;
+    }
+
+    @Override
+    public boolean okToAnimate() {
+        return true;
+    }
+
+    @Override
+    public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
+
+    }
+
+    @Override
+    public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered) {
+
+    }
+
+    @Override
+    public void enableKeyguard(boolean enabled) {
+
+    }
+
+    @Override
+    public void exitKeyguardSecurely(OnKeyguardExitResult callback) {
+
+    }
+
+    @Override
+    public boolean isKeyguardLocked() {
+        return keyguardShowingAndNotOccluded;
+    }
+
+    @Override
+    public boolean isKeyguardSecure(int userId) {
+        return false;
+    }
+
+    @Override
+    public boolean isKeyguardOccluded() {
+        return false;
+    }
+
+    @Override
+    public boolean isKeyguardTrustedLw() {
+        return false;
+    }
+
+    @Override
+    public boolean isKeyguardShowingAndNotOccluded() {
+        return keyguardShowingAndNotOccluded;
+    }
+
+    @Override
+    public boolean inKeyguardRestrictedKeyInputMode() {
+        return false;
+    }
+
+    @Override
+    public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback,
+            CharSequence message) {
+    }
+
+    @Override
+    public boolean isKeyguardDrawnLw() {
+        return false;
+    }
+
+    @Override
+    public boolean isShowingDreamLw() {
+        return false;
+    }
+
+    @Override
+    public void onKeyguardOccludedChangedLw(boolean occluded) {
+    }
+
+    @Override
+    public int rotationForOrientationLw(int orientation, int lastRotation, boolean defaultDisplay) {
+        return rotationToReport;
+    }
+
+    @Override
+    public boolean rotationHasCompatibleMetricsLw(int orientation, int rotation) {
+        return true;
+    }
+
+    @Override
+    public void setRotationLw(int rotation) {
+
+    }
+
+    @Override
+    public void setSafeMode(boolean safeMode) {
+
+    }
+
+    @Override
+    public void systemReady() {
+
+    }
+
+    @Override
+    public void systemBooted() {
+
+    }
+
+    @Override
+    public void showBootMessage(CharSequence msg, boolean always) {
+
+    }
+
+    @Override
+    public void hideBootMessages() {
+
+    }
+
+    @Override
+    public void userActivity() {
+
+    }
+
+    @Override
+    public void enableScreenAfterBoot() {
+
+    }
+
+    @Override
+    public void setCurrentOrientationLw(int newOrientation) {
+
+    }
+
+    @Override
+    public boolean performHapticFeedbackLw(WindowState win, int effectId,
+            boolean always) {
+        return false;
+    }
+
+    @Override
+    public void keepScreenOnStartedLw() {
+
+    }
+
+    @Override
+    public void keepScreenOnStoppedLw() {
+
+    }
+
+    @Override
+    public int getUserRotationMode() {
+        return 0;
+    }
+
+    @Override
+    public void setUserRotationMode(int mode,
+            int rotation) {
+
+    }
+
+    @Override
+    public int adjustSystemUiVisibilityLw(int visibility) {
+        return 0;
+    }
+
+    @Override
+    public boolean hasNavigationBar() {
+        return false;
+    }
+
+    @Override
+    public void lockNow(Bundle options) {
+
+    }
+
+    @Override
+    public void setLastInputMethodWindowLw(WindowState ime,
+            WindowState target) {
+
+    }
+
+    @Override
+    public void showRecentApps() {
+
+    }
+
+    @Override
+    public void showGlobalActions() {
+
+    }
+
+    @Override
+    public void setCurrentUserLw(int newUserId) {
+
+    }
+
+    @Override
+    public void setSwitchingUser(boolean switching) {
+
+    }
+
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+
+    }
+
+    @Override
+    public void dump(String prefix, PrintWriter writer, String[] args) {
+
+    }
+
+    @Override
+    public boolean isTopLevelWindow(int windowType) {
+        return false;
+    }
+
+    @Override
+    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+
+    }
+
+    @Override
+    public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+            DisplayCutout cutout, Rect outInsets) {
+
+    }
+
+    @Override
+    public boolean isNavBarForcedShownLw(WindowState win) {
+        return false;
+    }
+
+    @NavigationBarPosition
+    @Override
+    public int getNavBarPosition() {
+        return NAV_BAR_BOTTOM;
+    }
+
+    @Override
+    public void getNonDecorInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+            DisplayCutout cutout, Rect outInsets) {
+
+    }
+
+    @Override
+    public boolean isDockSideAllowed(int dockSide, int originalDockSide, int displayWidth,
+            int displayHeight, int displayRotation) {
+        return false;
+    }
+
+    @Override
+    public void onConfigurationChanged() {
+
+    }
+
+    @Override
+    public boolean shouldRotateSeamlessly(int oldRotation, int newRotation) {
+        return false;
+    }
+
+    @Override
+    public void setPipVisibilityLw(boolean visible) {
+
+    }
+
+    @Override
+    public void setRecentsVisibilityLw(boolean visible) {
+
+    }
+
+    @Override
+    public void setNavBarVirtualKeyHapticFeedbackEnabledLw(boolean enabled) {
+    }
+
+    @Override
+    public void onSystemUiStarted() {
+    }
+
+    @Override
+    public boolean canDismissBootAnimation() {
+        return true;
+    }
+
+    @Override
+    public void requestUserActivityNotification() {
+    }
+
+    @Override
+    public void onLockTaskStateChangedLw(int lockTaskState) {
+    }
+
+    @Override
+    public boolean setAodShowing(boolean aodShowing) {
+        return false;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
new file mode 100644
index 0000000..a5c47de
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 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 junit.framework.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link AppTransition}.
+ *
+ * runtest frameworks-services -c com.android.server.wm.UnknownAppVisibilityControllerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class UnknownAppVisibilityControllerTest extends WindowTestsBase {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        sWm.mUnknownAppVisibilityController.clear();
+    }
+
+    @Test
+    public void testFlow() throws Exception {
+        final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+        sWm.mUnknownAppVisibilityController.notifyLaunched(token);
+        sWm.mUnknownAppVisibilityController.notifyAppResumedFinished(token);
+        sWm.mUnknownAppVisibilityController.notifyRelayouted(token);
+
+        // Make sure our handler processed the message.
+        Thread.sleep(100);
+        assertTrue(sWm.mUnknownAppVisibilityController.allResolved());
+    }
+
+    @Test
+    public void testMultiple() throws Exception {
+        final AppWindowToken token1 = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+        final AppWindowToken token2 = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+        sWm.mUnknownAppVisibilityController.notifyLaunched(token1);
+        sWm.mUnknownAppVisibilityController.notifyAppResumedFinished(token1);
+        sWm.mUnknownAppVisibilityController.notifyLaunched(token2);
+        sWm.mUnknownAppVisibilityController.notifyRelayouted(token1);
+        sWm.mUnknownAppVisibilityController.notifyAppResumedFinished(token2);
+        sWm.mUnknownAppVisibilityController.notifyRelayouted(token2);
+
+        // Make sure our handler processed the message.
+        Thread.sleep(100);
+        assertTrue(sWm.mUnknownAppVisibilityController.allResolved());
+    }
+
+    @Test
+    public void testClear() throws Exception {
+        final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+        sWm.mUnknownAppVisibilityController.notifyLaunched(token);
+        sWm.mUnknownAppVisibilityController.clear();;
+        assertTrue(sWm.mUnknownAppVisibilityController.allResolved());
+    }
+
+    @Test
+    public void testAppRemoved() throws Exception {
+        final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+        sWm.mUnknownAppVisibilityController.notifyLaunched(token);
+        sWm.mUnknownAppVisibilityController.appRemovedOrHidden(token);
+        assertTrue(sWm.mUnknownAppVisibilityController.allResolved());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/WallpaperControllerTests.java
new file mode 100644
index 0000000..71ead20
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -0,0 +1,67 @@
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link WallpaperController} class.
+ *
+ * Build/Install/Run:
+ *  atest com.android.server.wm.WallpaperControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WallpaperControllerTests extends WindowTestsBase {
+    @Test
+    public void testWallpaperScreenshot() {
+        WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class);
+
+        synchronized (sWm.mWindowMap) {
+            // No wallpaper
+            final DisplayContent dc = createNewDisplay();
+            Bitmap wallpaperBitmap = sWm.mRoot.mWallpaperController.screenshotWallpaperLocked();
+            assertNull(wallpaperBitmap);
+
+            // No wallpaper WSA Surface
+            WindowToken wallpaperWindowToken = new WallpaperWindowToken(sWm, mock(IBinder.class),
+                    true, dc, true /* ownerCanManageAppTokens */);
+            WindowState wallpaperWindow = createWindow(null /* parent */, TYPE_WALLPAPER,
+                    wallpaperWindowToken, "wallpaperWindow");
+            wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked();
+            assertNull(wallpaperBitmap);
+
+            // Wallpaper with not visible WSA surface.
+            wallpaperWindow.mWinAnimator.mSurfaceController = windowSurfaceController;
+            wallpaperWindow.mWinAnimator.mLastAlpha = 1;
+            wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked();
+            assertNull(wallpaperBitmap);
+
+            when(windowSurfaceController.getShown()).thenReturn(true);
+
+            // Wallpaper with WSA alpha set to 0.
+            wallpaperWindow.mWinAnimator.mLastAlpha = 0;
+            wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked();
+            assertNull(wallpaperBitmap);
+
+            // Wallpaper window with WSA Surface
+            wallpaperWindow.mWinAnimator.mLastAlpha = 1;
+            wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked();
+            assertNotNull(wallpaperBitmap);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java
new file mode 100644
index 0000000..ca520ed
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2016 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 com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
+
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.ClipRectAnimation;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link WindowAnimationSpec} class.
+ *
+ *  atest FrameworksServicesTests:com.android.server.wm.WindowAnimationSpecTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowAnimationSpecTest {
+    private final SurfaceControl mSurfaceControl = mock(SurfaceControl.class);
+    private final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class);
+    private final Animation mAnimation = mock(Animation.class);
+    private final Rect mStackBounds = new Rect(0, 0, 10, 10);
+
+    @Test
+    public void testApply_clipNone() {
+        Rect windowCrop = new Rect(0, 0, 20, 20);
+        Animation a = createClipRectAnimation(windowCrop, windowCrop);
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_NONE,
+                true /* isAppAnimation */);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
+                argThat(rect -> rect.equals(windowCrop)));
+    }
+
+    @Test
+    public void testApply_clipAfter() {
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null,
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_AFTER_ANIM,
+                true /* isAppAnimation */);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty));
+        verify(mTransaction).setFinalCrop(eq(mSurfaceControl),
+                argThat(rect -> rect.equals(mStackBounds)));
+    }
+
+    @Test
+    public void testApply_clipAfterOffsetPosition() {
+        // Stack bounds is (0, 0, 10, 10) position is (20, 40)
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation,
+                new Point(20, 40), mStackBounds, false /* canSkipFirstFrame */,
+                STACK_CLIP_AFTER_ANIM,
+                true /* isAppAnimation */);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty));
+        verify(mTransaction).setFinalCrop(eq(mSurfaceControl),
+                argThat(rect -> rect.left == 20 && rect.top == 40 && rect.right == 30
+                        && rect.bottom == 50));
+    }
+
+    @Test
+    public void testApply_clipBeforeNoAnimationBounds() {
+        // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 0, 0)
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null,
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM,
+                true /* isAppAnimation */);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
+                argThat(rect -> rect.equals(mStackBounds)));
+    }
+
+    @Test
+    public void testApply_clipBeforeNoStackBounds() {
+        // Stack bounds is (0, 0, 0, 0) animation clip is (0, 0, 20, 20)
+        Rect windowCrop = new Rect(0, 0, 20, 20);
+        Animation a = createClipRectAnimation(windowCrop, windowCrop);
+        a.initialize(0, 0, 0, 0);
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
+                null, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM,
+                true /* isAppAnimation */);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty));
+    }
+
+    @Test
+    public void testApply_clipBeforeSmallerAnimationClip() {
+        // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 5, 5)
+        Rect windowCrop = new Rect(0, 0, 5, 5);
+        Animation a = createClipRectAnimation(windowCrop, windowCrop);
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM,
+                true /* isAppAnimation */);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
+                argThat(rect -> rect.equals(windowCrop)));
+    }
+
+    @Test
+    public void testApply_clipBeforeSmallerStackClip() {
+        // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 20, 20)
+        Rect windowCrop = new Rect(0, 0, 20, 20);
+        Animation a = createClipRectAnimation(windowCrop, windowCrop);
+        WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM,
+                true /* isAppAnimation */);
+        windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
+        verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
+                argThat(rect -> rect.equals(mStackBounds)));
+    }
+
+    private Animation createClipRectAnimation(Rect fromClip, Rect toClip) {
+        Animation a = new ClipRectAnimation(fromClip, toClip);
+        a.initialize(0, 0, 0, 0);
+        return a;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
new file mode 100644
index 0000000..513c1ec
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2017 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 org.junit.Test;
+
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.DisplayInfo;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
+import static android.app.WindowConfiguration.WINDOW_CONFIG_WINDOWING_MODE;
+import static android.content.pm.ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test class to for {@link android.app.WindowConfiguration}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.WindowConfigurationTests
+ */
+@SmallTest
+@FlakyTest(bugId = 74078662)
+@Presubmit
+@org.junit.runner.RunWith(AndroidJUnit4.class)
+public class WindowConfigurationTests extends WindowTestsBase {
+    private Rect mParentBounds;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/);
+    }
+
+    /** Tests {@link android.app.WindowConfiguration#diff(WindowConfiguration, boolean)}. */
+    @Test
+    public void testDiff() {
+        final Configuration config1 = new Configuration();
+        final WindowConfiguration winConfig1 = config1.windowConfiguration;
+        final Configuration config2 = new Configuration();
+        final WindowConfiguration winConfig2 = config2.windowConfiguration;
+        final Configuration config3 = new Configuration();
+        final WindowConfiguration winConfig3 = config3.windowConfiguration;
+
+        winConfig1.setAppBounds(0, 1, 1, 0);
+        winConfig2.setAppBounds(1, 2, 2, 1);
+        winConfig3.setAppBounds(winConfig1.getAppBounds());
+
+
+        assertEquals(CONFIG_WINDOW_CONFIGURATION, config1.diff(config2));
+        assertEquals(0, config1.diffPublicOnly(config2));
+        assertEquals(WINDOW_CONFIG_APP_BOUNDS,
+                winConfig1.diff(winConfig2, false /* compareUndefined */));
+
+        winConfig2.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertEquals(WINDOW_CONFIG_APP_BOUNDS | WINDOW_CONFIG_WINDOWING_MODE,
+                winConfig1.diff(winConfig2, false /* compareUndefined */));
+
+        assertEquals(0, config1.diff(config3));
+        assertEquals(0, config1.diffPublicOnly(config3));
+        assertEquals(0, winConfig1.diff(winConfig3, false /* compareUndefined */));
+    }
+
+    /** Tests {@link android.app.WindowConfiguration#compareTo(WindowConfiguration)}. */
+    @Test
+    public void testConfigurationCompareTo() throws Exception {
+        final Configuration blankConfig = new Configuration();
+        final WindowConfiguration blankWinConfig = new WindowConfiguration();
+
+        final Configuration config1 = new Configuration();
+        final WindowConfiguration winConfig1 = config1.windowConfiguration;
+        winConfig1.setAppBounds(1, 2, 3, 4);
+
+        final Configuration config2 = new Configuration(config1);
+        final WindowConfiguration winConfig2 = config2.windowConfiguration;
+
+        assertEquals(config1.compareTo(config2), 0);
+        assertEquals(winConfig1.compareTo(winConfig2), 0);
+
+        // Different windowing mode
+        winConfig2.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertNotEquals(config1.compareTo(config2), 0);
+        assertNotEquals(winConfig1.compareTo(winConfig2), 0);
+        winConfig2.setWindowingMode(winConfig1.getWindowingMode());
+
+        // Different bounds
+        winConfig2.setAppBounds(0, 2, 3, 4);
+        assertNotEquals(config1.compareTo(config2), 0);
+        assertNotEquals(winConfig1.compareTo(winConfig2), 0);
+
+        // No bounds
+        assertEquals(config1.compareTo(blankConfig), -1);
+        assertEquals(winConfig1.compareTo(blankWinConfig), -1);
+
+        assertEquals(blankConfig.compareTo(config1), 1);
+        assertEquals(blankWinConfig.compareTo(winConfig1), 1);
+    }
+
+    @Test
+    public void testSetActivityType() throws Exception {
+        final WindowConfiguration config = new WindowConfiguration();
+        config.setActivityType(ACTIVITY_TYPE_HOME);
+        assertEquals(ACTIVITY_TYPE_HOME, config.getActivityType());
+
+        // Allowed to change from app process.
+        config.setActivityType(ACTIVITY_TYPE_STANDARD);
+        assertEquals(ACTIVITY_TYPE_STANDARD, config.getActivityType());
+    }
+
+    /** Ensures the configuration app bounds at the root level match the app dimensions. */
+    @Test
+    public void testAppBounds_RootConfigurationBounds() throws Exception {
+        final DisplayInfo info = mDisplayContent.getDisplayInfo();
+        info.appWidth = 1024;
+        info.appHeight = 768;
+
+        final Rect appBounds = sWm.computeNewConfiguration(
+                mDisplayContent.getDisplayId()).windowConfiguration.getAppBounds();
+        // The bounds should always be positioned in the top left.
+        assertEquals(appBounds.left, 0);
+        assertEquals(appBounds.top, 0);
+
+        // The bounds should equal the defined app width and height
+        assertEquals(appBounds.width(), info.appWidth);
+        assertEquals(appBounds.height(), info.appHeight);
+    }
+
+    /** Ensures that bounds are clipped to their parent. */
+    @Test
+    public void testAppBounds_BoundsClipping() throws Exception {
+        final Rect shiftedBounds = new Rect(mParentBounds);
+        shiftedBounds.offset(10, 10);
+        final Rect expectedBounds = new Rect(mParentBounds);
+        expectedBounds.intersect(shiftedBounds);
+        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds,
+                expectedBounds);
+    }
+
+    /** Ensures that empty bounds are not propagated to the configuration. */
+    @Test
+    public void testAppBounds_EmptyBounds() throws Exception {
+        final Rect emptyBounds = new Rect();
+        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds,
+                null /*ExpectedBounds*/);
+    }
+
+    /** Ensures that bounds on freeform stacks are not clipped. */
+    @Test
+    public void testAppBounds_FreeFormBounds() throws Exception {
+        final Rect freeFormBounds = new Rect(mParentBounds);
+        freeFormBounds.offset(10, 10);
+        testStackBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds,
+                freeFormBounds);
+    }
+
+    /** Ensures that fully contained bounds are not clipped. */
+    @Test
+    public void testAppBounds_ContainedBounds() throws Exception {
+        final Rect insetBounds = new Rect(mParentBounds);
+        insetBounds.inset(5, 5, 5, 5);
+        testStackBoundsConfiguration(
+                WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds);
+    }
+
+    /** Ensures that full screen free form bounds are clipped */
+    @Test
+    public void testAppBounds_FullScreenFreeFormBounds() throws Exception {
+        final Rect fullScreenBounds = new Rect(0, 0, mDisplayInfo.logicalWidth,
+                mDisplayInfo.logicalHeight);
+        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds,
+                mParentBounds);
+    }
+
+    private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
+            Rect expectedConfigBounds) {
+        final StackWindowController stackController = createStackControllerOnStackOnDisplay(
+                        windowingMode, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+
+        final Configuration parentConfig = mDisplayContent.getConfiguration();
+        parentConfig.windowConfiguration.setAppBounds(parentBounds);
+
+        final Configuration config = new Configuration();
+        final WindowConfiguration winConfig = config.windowConfiguration;
+        stackController.adjustConfigurationForBounds(bounds, null /*insetBounds*/,
+                new Rect() /*nonDecorBounds*/, new Rect() /*stableBounds*/, false /*overrideWidth*/,
+                false /*overrideHeight*/, mDisplayInfo.logicalDensityDpi, config, parentConfig,
+                windowingMode);
+        // Assert that both expected and actual are null or are equal to each other
+
+        assertEquals(expectedConfigBounds, winConfig.getAppBounds());
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerControllerTests.java
new file mode 100644
index 0000000..502cb6e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerControllerTests.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 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 android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.support.test.filters.FlakyTest;
+import org.junit.Test;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.content.res.Configuration.EMPTY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test class for {@link WindowContainerController}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.WindowContainerControllerTests
+ */
+@SmallTest
+@Presubmit
+@FlakyTest(bugId = 74078662)
+@org.junit.runner.RunWith(AndroidJUnit4.class)
+public class WindowContainerControllerTests extends WindowTestsBase {
+
+    @Test
+    public void testCreation() throws Exception {
+        final WindowContainerController controller = new WindowContainerController(null, sWm);
+        final WindowContainer container = new WindowContainer(sWm);
+
+        container.setController(controller);
+        assertEquals(controller, container.getController());
+        assertEquals(controller.mContainer, container);
+    }
+
+    @Test
+    public void testSetContainer() throws Exception {
+        final WindowContainerController controller = new WindowContainerController(null, sWm);
+        final WindowContainer container = new WindowContainer(sWm);
+
+        controller.setContainer(container);
+        assertEquals(controller.mContainer, container);
+
+        // Assert we can't change the container to another one once set
+        boolean gotException = false;
+        try {
+            controller.setContainer(new WindowContainer(sWm));
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+
+        // Assert that we can set the container to null.
+        controller.setContainer(null);
+        assertNull(controller.mContainer);
+    }
+
+    @Test
+    public void testRemoveContainer() throws Exception {
+        final WindowContainerController controller = new WindowContainerController(null, sWm);
+        final WindowContainer container = new WindowContainer(sWm);
+
+        controller.setContainer(container);
+        assertEquals(controller.mContainer, container);
+
+        controller.removeContainer();
+        assertNull(controller.mContainer);
+    }
+
+    @Test
+    public void testOnOverrideConfigurationChanged() throws Exception {
+        final WindowContainerController controller = new WindowContainerController(null, sWm);
+        final WindowContainer container = new WindowContainer(sWm);
+
+        controller.setContainer(container);
+        assertEquals(controller.mContainer, container);
+        assertEquals(EMPTY, container.getOverrideConfiguration());
+
+        final Configuration config = new Configuration();
+        config.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        config.windowConfiguration.setAppBounds(10, 10, 10, 10);
+
+        // Assert that the config change through the controller is propagated to the container.
+        controller.onOverrideConfigurationChanged(config);
+        assertEquals(config, container.getOverrideConfiguration());
+
+        // Assert the container configuration isn't changed after removal from the controller.
+        controller.removeContainer();
+        controller.onOverrideConfigurationChanged(EMPTY);
+        assertEquals(config, container.getOverrideConfiguration());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
new file mode 100644
index 0000000..6c7830e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -0,0 +1,872 @@
+/*
+ * Copyright (C) 2016 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 android.support.test.filters.FlakyTest;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.util.Comparator;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Test class for {@link WindowContainer}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:com.android.server.wm.WindowContainerTests
+ */
+@SmallTest
+@Presubmit
+@FlakyTest(bugId = 74078662)
+@RunWith(AndroidJUnit4.class)
+public class WindowContainerTests extends WindowTestsBase {
+
+    @Test
+    public void testCreation() throws Exception {
+        final TestWindowContainer w = new TestWindowContainerBuilder().setLayer(0).build();
+        assertNull("window must have no parent", w.getParentWindow());
+        assertEquals("window must have no children", 0, w.getChildrenCount());
+    }
+
+    @Test
+    public void testAdd() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer layer1 = root.addChildWindow(builder.setLayer(1));
+        final TestWindowContainer secondLayer1 = root.addChildWindow(builder.setLayer(1));
+        final TestWindowContainer layer2 = root.addChildWindow(builder.setLayer(2));
+        final TestWindowContainer layerNeg1 = root.addChildWindow(builder.setLayer(-1));
+        final TestWindowContainer layerNeg2 = root.addChildWindow(builder.setLayer(-2));
+        final TestWindowContainer secondLayerNeg1 = root.addChildWindow(builder.setLayer(-1));
+        final TestWindowContainer layer0 = root.addChildWindow(builder.setLayer(0));
+
+        assertEquals(7, root.getChildrenCount());
+
+        assertEquals(root, layer1.getParentWindow());
+        assertEquals(root, secondLayer1.getParentWindow());
+        assertEquals(root, layer2.getParentWindow());
+        assertEquals(root, layerNeg1.getParentWindow());
+        assertEquals(root, layerNeg2.getParentWindow());
+        assertEquals(root, secondLayerNeg1.getParentWindow());
+        assertEquals(root, layer0.getParentWindow());
+
+        assertEquals(layerNeg2, root.getChildAt(0));
+        assertEquals(secondLayerNeg1, root.getChildAt(1));
+        assertEquals(layerNeg1, root.getChildAt(2));
+        assertEquals(layer0, root.getChildAt(3));
+        assertEquals(layer1, root.getChildAt(4));
+        assertEquals(secondLayer1, root.getChildAt(5));
+        assertEquals(layer2, root.getChildAt(6));
+
+        assertTrue(layer1.mOnParentSetCalled);
+        assertTrue(secondLayer1.mOnParentSetCalled);
+        assertTrue(layer2.mOnParentSetCalled);
+        assertTrue(layerNeg1.mOnParentSetCalled);
+        assertTrue(layerNeg2.mOnParentSetCalled);
+        assertTrue(secondLayerNeg1.mOnParentSetCalled);
+        assertTrue(layer0.mOnParentSetCalled);
+    }
+
+    @Test
+    public void testAddChildSetsSurfacePosition() throws Exception {
+        MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer();
+
+        final SurfaceControl.Transaction transaction = mock(SurfaceControl.Transaction.class);
+        sWm.mTransactionFactory = () -> transaction;
+
+        WindowContainer child = new WindowContainer(sWm);
+        child.setBounds(1, 1, 10, 10);
+
+        verify(transaction, never()).setPosition(any(), anyFloat(), anyFloat());
+        top.addChild(child, 0);
+        verify(transaction, times(1)).setPosition(any(), eq(1.f), eq(1.f));
+    }
+
+    @Test
+    public void testAdd_AlreadyHasParent() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+
+        boolean gotException = false;
+        try {
+            child1.addChildWindow(child2);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+
+        gotException = false;
+        try {
+            root.addChildWindow(child2);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    @Test
+    public void testHasChild() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow();
+        final TestWindowContainer child21 = child2.addChildWindow();
+
+        assertEquals(2, root.getChildrenCount());
+        assertEquals(2, child1.getChildrenCount());
+        assertEquals(1, child2.getChildrenCount());
+
+        assertTrue(root.hasChild(child1));
+        assertTrue(root.hasChild(child2));
+        assertTrue(root.hasChild(child11));
+        assertTrue(root.hasChild(child12));
+        assertTrue(root.hasChild(child21));
+
+        assertTrue(child1.hasChild(child11));
+        assertTrue(child1.hasChild(child12));
+        assertFalse(child1.hasChild(child21));
+
+        assertTrue(child2.hasChild(child21));
+        assertFalse(child2.hasChild(child11));
+        assertFalse(child2.hasChild(child12));
+    }
+
+    @Test
+    public void testRemoveImmediately() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow();
+        final TestWindowContainer child21 = child2.addChildWindow();
+
+        assertNotNull(child12.getParentWindow());
+        child12.removeImmediately();
+        assertNull(child12.getParentWindow());
+        assertEquals(1, child1.getChildrenCount());
+        assertFalse(child1.hasChild(child12));
+        assertFalse(root.hasChild(child12));
+
+        assertTrue(root.hasChild(child2));
+        assertNotNull(child2.getParentWindow());
+        child2.removeImmediately();
+        assertNull(child2.getParentWindow());
+        assertNull(child21.getParentWindow());
+        assertEquals(0, child2.getChildrenCount());
+        assertEquals(1, root.getChildrenCount());
+        assertFalse(root.hasChild(child2));
+        assertFalse(root.hasChild(child21));
+
+        assertTrue(root.hasChild(child1));
+        assertTrue(root.hasChild(child11));
+
+        root.removeImmediately();
+        assertEquals(0, root.getChildrenCount());
+    }
+
+    @Test
+    public void testRemoveImmediately_WithController() throws Exception {
+        final WindowContainer container = new WindowContainer(sWm);
+        final WindowContainerController controller = new WindowContainerController(null, sWm);
+
+        container.setController(controller);
+        assertEquals(controller, container.getController());
+        assertEquals(container, controller.mContainer);
+
+        container.removeImmediately();
+        assertNull(container.getController());
+        assertNull(controller.mContainer);
+    }
+
+    @Test
+    public void testSetController() throws Exception {
+        final WindowContainerController controller = new WindowContainerController(null, sWm);
+        final WindowContainer container = new WindowContainer(sWm);
+
+        container.setController(controller);
+        assertEquals(controller, container.getController());
+        assertEquals(container, controller.mContainer);
+
+        // Assert we can't change the controller to another one once set
+        boolean gotException = false;
+        try {
+            container.setController(new WindowContainerController(null, sWm));
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+
+        // Assert that we can set the controller to null.
+        container.setController(null);
+        assertNull(container.getController());
+        assertNull(controller.mContainer);
+    }
+
+    @Test
+    public void testPositionChildAt() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+        final TestWindowContainer child3 = root.addChildWindow();
+
+        // Test position at top.
+        root.positionChildAt(POSITION_TOP, child1, false /* includingParents */);
+        assertEquals(child1, root.getChildAt(root.getChildrenCount() - 1));
+
+        // Test position at bottom.
+        root.positionChildAt(POSITION_BOTTOM, child1, false /* includingParents */);
+        assertEquals(child1, root.getChildAt(0));
+
+        // Test position in the middle.
+        root.positionChildAt(1, child3, false /* includingParents */);
+        assertEquals(child1, root.getChildAt(0));
+        assertEquals(child3, root.getChildAt(1));
+        assertEquals(child2, root.getChildAt(2));
+    }
+
+    @Test
+    public void testPositionChildAtIncludeParents() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow();
+        final TestWindowContainer child13 = child1.addChildWindow();
+        final TestWindowContainer child21 = child2.addChildWindow();
+        final TestWindowContainer child22 = child2.addChildWindow();
+        final TestWindowContainer child23 = child2.addChildWindow();
+
+        // Test moving to top.
+        child1.positionChildAt(POSITION_TOP, child11, true /* includingParents */);
+        assertEquals(child12, child1.getChildAt(0));
+        assertEquals(child13, child1.getChildAt(1));
+        assertEquals(child11, child1.getChildAt(2));
+        assertEquals(child2, root.getChildAt(0));
+        assertEquals(child1, root.getChildAt(1));
+
+        // Test moving to bottom.
+        child1.positionChildAt(POSITION_BOTTOM, child11, true /* includingParents */);
+        assertEquals(child11, child1.getChildAt(0));
+        assertEquals(child12, child1.getChildAt(1));
+        assertEquals(child13, child1.getChildAt(2));
+        assertEquals(child1, root.getChildAt(0));
+        assertEquals(child2, root.getChildAt(1));
+
+        // Test moving to middle, includeParents shouldn't do anything.
+        child2.positionChildAt(1, child21, true /* includingParents */);
+        assertEquals(child11, child1.getChildAt(0));
+        assertEquals(child12, child1.getChildAt(1));
+        assertEquals(child13, child1.getChildAt(2));
+        assertEquals(child22, child2.getChildAt(0));
+        assertEquals(child21, child2.getChildAt(1));
+        assertEquals(child23, child2.getChildAt(2));
+        assertEquals(child1, root.getChildAt(0));
+        assertEquals(child2, root.getChildAt(1));
+    }
+
+    @Test
+    public void testPositionChildAtInvalid() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+
+        boolean gotException = false;
+        try {
+            // Check response to negative position.
+            root.positionChildAt(-1, child1, false /* includingParents */);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+
+        gotException = false;
+        try {
+            // Check response to position that's bigger than child number.
+            root.positionChildAt(3, child1, false /* includingParents */);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    @Test
+    public void testIsAnimating() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow(builder.setIsAnimating(true));
+        final TestWindowContainer child2 = root.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow(builder.setIsAnimating(true));
+        final TestWindowContainer child21 = child2.addChildWindow();
+
+        assertFalse(root.isAnimating());
+        assertTrue(child1.isAnimating());
+        assertTrue(child11.isAnimating());
+        assertTrue(child12.isAnimating());
+        assertFalse(child2.isAnimating());
+        assertFalse(child21.isAnimating());
+
+        assertTrue(root.isSelfOrChildAnimating());
+        assertTrue(child1.isSelfOrChildAnimating());
+        assertFalse(child11.isSelfOrChildAnimating());
+        assertTrue(child12.isSelfOrChildAnimating());
+        assertFalse(child2.isSelfOrChildAnimating());
+        assertFalse(child21.isSelfOrChildAnimating());
+    }
+
+    @Test
+    public void testIsVisible() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow(builder.setIsVisible(true));
+        final TestWindowContainer child2 = root.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow(builder.setIsVisible(true));
+        final TestWindowContainer child21 = child2.addChildWindow();
+
+        assertFalse(root.isVisible());
+        assertTrue(child1.isVisible());
+        assertFalse(child11.isVisible());
+        assertTrue(child12.isVisible());
+        assertFalse(child2.isVisible());
+        assertFalse(child21.isVisible());
+    }
+
+    @Test
+    public void testOverrideConfigurationAncestorNotification() {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer grandparent = builder.setLayer(0).build();
+
+        final TestWindowContainer parent = grandparent.addChildWindow();
+        final TestWindowContainer child = parent.addChildWindow();
+        child.onOverrideConfigurationChanged(new Configuration());
+
+        assertTrue(grandparent.mOnDescendantOverrideCalled);
+    }
+
+    @Test
+    public void testRemoveChild() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child21 = child2.addChildWindow();
+
+        assertTrue(root.hasChild(child2));
+        assertTrue(root.hasChild(child21));
+        root.removeChild(child2);
+        assertFalse(root.hasChild(child2));
+        assertFalse(root.hasChild(child21));
+        assertNull(child2.getParentWindow());
+
+        boolean gotException = false;
+        assertTrue(root.hasChild(child11));
+        try {
+            // Can only detach our direct children.
+            root.removeChild(child11);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    @Test
+    public void testGetOrientation_childSpecified() throws Exception {
+        testGetOrientation_childSpecifiedConfig(false, SCREEN_ORIENTATION_LANDSCAPE,
+            SCREEN_ORIENTATION_LANDSCAPE);
+        testGetOrientation_childSpecifiedConfig(false, SCREEN_ORIENTATION_UNSET,
+            SCREEN_ORIENTATION_UNSPECIFIED);
+    }
+
+    private void testGetOrientation_childSpecifiedConfig(boolean childVisible, int childOrientation,
+        int expectedOrientation) {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+        root.setFillsParent(true);
+
+        builder.setIsVisible(childVisible);
+
+        if (childOrientation != SCREEN_ORIENTATION_UNSET) {
+            builder.setOrientation(childOrientation);
+        }
+
+        final TestWindowContainer child1 = root.addChildWindow(builder);
+        child1.setFillsParent(true);
+
+        assertEquals(expectedOrientation, root.getOrientation());
+    }
+
+    @Test
+    public void testGetOrientation_Unset() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
+        // Unspecified well because we didn't specify anything...
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, root.getOrientation());
+    }
+
+    @Test
+    public void testGetOrientation_InvisibleParentUnsetVisibleChildren() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
+
+        builder.setIsVisible(false).setLayer(-1);
+        final TestWindowContainer invisible = root.addChildWindow(builder);
+        builder.setIsVisible(true).setLayer(-2);
+        final TestWindowContainer invisibleChild1VisibleAndSet = invisible.addChildWindow(builder);
+        invisibleChild1VisibleAndSet.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        // Landscape well because the container is visible and that is what we set on it above.
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, invisibleChild1VisibleAndSet.getOrientation());
+        // Landscape because even though the container isn't visible it has a child that is
+        // specifying it can influence the orientation by being visible.
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, invisible.getOrientation());
+        // Landscape because the grandchild is visible and therefore can participate.
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, root.getOrientation());
+
+        builder.setIsVisible(true).setLayer(-3);
+        final TestWindowContainer visibleUnset = root.addChildWindow(builder);
+        visibleUnset.setOrientation(SCREEN_ORIENTATION_UNSET);
+        assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnset.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, root.getOrientation());
+
+    }
+
+    @Test
+    public void testGetOrientation_setBehind() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
+
+        builder.setIsVisible(true).setLayer(-1);
+        final TestWindowContainer visibleUnset = root.addChildWindow(builder);
+        visibleUnset.setOrientation(SCREEN_ORIENTATION_UNSET);
+
+        builder.setIsVisible(true).setLayer(-2);
+        final TestWindowContainer visibleUnsetChild1VisibleSetBehind =
+                visibleUnset.addChildWindow(builder);
+        visibleUnsetChild1VisibleSetBehind.setOrientation(SCREEN_ORIENTATION_BEHIND);
+        // Setting to visible behind will be used by the parents if there isn't another other
+        // container behind this one that has an orientation set.
+        assertEquals(SCREEN_ORIENTATION_BEHIND,
+                visibleUnsetChild1VisibleSetBehind.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_BEHIND, visibleUnset.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation());
+    }
+
+    @Test
+    public void testGetOrientation_fillsParent() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
+
+        builder.setIsVisible(true).setLayer(-1);
+        final TestWindowContainer visibleUnset = root.addChildWindow(builder);
+        visibleUnset.setOrientation(SCREEN_ORIENTATION_BEHIND);
+
+        builder.setLayer(1).setIsVisible(true);
+        final TestWindowContainer visibleUnspecifiedRootChild = root.addChildWindow(builder);
+        visibleUnspecifiedRootChild.setFillsParent(false);
+        visibleUnspecifiedRootChild.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
+        // Unset because the child doesn't fill the parent. May as well be invisible...
+        assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnspecifiedRootChild.getOrientation());
+        // The parent uses whatever orientation is set behind this container since it doesn't fill
+        // the parent.
+        assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation());
+
+        // Test case of child filling its parent, but its parent isn't filling its own parent.
+        builder.setLayer(2).setIsVisible(true);
+        final TestWindowContainer visibleUnspecifiedRootChildChildFillsParent =
+                visibleUnspecifiedRootChild.addChildWindow(builder);
+        visibleUnspecifiedRootChildChildFillsParent.setOrientation(
+                SCREEN_ORIENTATION_PORTRAIT);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT,
+                visibleUnspecifiedRootChildChildFillsParent.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnspecifiedRootChild.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation());
+
+
+        visibleUnspecifiedRootChild.setFillsParent(true);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, visibleUnspecifiedRootChild.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, root.getOrientation());
+    }
+
+    @Test
+    public void testCompareTo() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow();
+
+        final TestWindowContainer child2 = root.addChildWindow();
+        final TestWindowContainer child21 = child2.addChildWindow();
+        final TestWindowContainer child22 = child2.addChildWindow();
+        final TestWindowContainer child23 = child2.addChildWindow();
+        final TestWindowContainer child221 = child22.addChildWindow();
+        final TestWindowContainer child222 = child22.addChildWindow();
+        final TestWindowContainer child223 = child22.addChildWindow();
+        final TestWindowContainer child2221 = child222.addChildWindow();
+        final TestWindowContainer child2222 = child222.addChildWindow();
+        final TestWindowContainer child2223 = child222.addChildWindow();
+
+        final TestWindowContainer root2 = builder.setLayer(0).build();
+
+        assertEquals(0, root.compareTo(root));
+        assertEquals(-1, child1.compareTo(child2));
+        assertEquals(1, child2.compareTo(child1));
+
+        boolean inTheSameTree = true;
+        try {
+            root.compareTo(root2);
+        } catch (IllegalArgumentException e) {
+            inTheSameTree = false;
+        }
+        assertFalse(inTheSameTree);
+
+        assertEquals(-1, child1.compareTo(child11));
+        assertEquals(1, child21.compareTo(root));
+        assertEquals(1, child21.compareTo(child12));
+        assertEquals(-1, child11.compareTo(child2));
+        assertEquals(1, child2221.compareTo(child11));
+        assertEquals(-1, child2222.compareTo(child223));
+        assertEquals(1, child2223.compareTo(child21));
+    }
+
+    @Test
+    public void testPrefixOrderIndex() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow();
+
+        final TestWindowContainer child2 = root.addChildWindow();
+
+        final TestWindowContainer child21 = child2.addChildWindow();
+        final TestWindowContainer child22 = child2.addChildWindow();
+
+        final TestWindowContainer child221 = child22.addChildWindow();
+        final TestWindowContainer child222 = child22.addChildWindow();
+        final TestWindowContainer child223 = child22.addChildWindow();
+
+        final TestWindowContainer child23 = child2.addChildWindow();
+
+        assertEquals(0, root.getPrefixOrderIndex());
+        assertEquals(1, child1.getPrefixOrderIndex());
+        assertEquals(2, child11.getPrefixOrderIndex());
+        assertEquals(3, child12.getPrefixOrderIndex());
+        assertEquals(4, child2.getPrefixOrderIndex());
+        assertEquals(5, child21.getPrefixOrderIndex());
+        assertEquals(6, child22.getPrefixOrderIndex());
+        assertEquals(7, child221.getPrefixOrderIndex());
+        assertEquals(8, child222.getPrefixOrderIndex());
+        assertEquals(9, child223.getPrefixOrderIndex());
+        assertEquals(10, child23.getPrefixOrderIndex());
+    }
+
+    @Test
+    public void testPrefixOrder_addEntireSubtree() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.build();
+        final TestWindowContainer subtree = builder.build();
+        final TestWindowContainer subtree2 = builder.build();
+
+        final TestWindowContainer child1 = subtree.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child2 = subtree2.addChildWindow();
+        final TestWindowContainer child3 = subtree2.addChildWindow();
+        subtree.addChild(subtree2, 1);
+        root.addChild(subtree, 0);
+
+        assertEquals(0, root.getPrefixOrderIndex());
+        assertEquals(1, subtree.getPrefixOrderIndex());
+        assertEquals(2, child1.getPrefixOrderIndex());
+        assertEquals(3, child11.getPrefixOrderIndex());
+        assertEquals(4, subtree2.getPrefixOrderIndex());
+        assertEquals(5, child2.getPrefixOrderIndex());
+        assertEquals(6, child3.getPrefixOrderIndex());
+    }
+
+    @Test
+    public void testPrefixOrder_remove() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow();
+
+        final TestWindowContainer child2 = root.addChildWindow();
+
+        assertEquals(0, root.getPrefixOrderIndex());
+        assertEquals(1, child1.getPrefixOrderIndex());
+        assertEquals(2, child11.getPrefixOrderIndex());
+        assertEquals(3, child12.getPrefixOrderIndex());
+        assertEquals(4, child2.getPrefixOrderIndex());
+
+        root.removeChild(child1);
+
+        assertEquals(1, child2.getPrefixOrderIndex());
+    }
+
+    /**
+     * Ensure children of a {@link WindowContainer} do not have
+     * {@link WindowContainer#onParentResize()} called when {@link WindowContainer#onParentResize()}
+     * is invoked with overridden bounds.
+     */
+    @Test
+    public void testOnParentResizePropagation() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.build();
+
+        final TestWindowContainer child = root.addChildWindow();
+        child.setBounds(new Rect(1,1,2,2));
+
+        final TestWindowContainer grandChild = mock(TestWindowContainer.class);
+
+        child.addChildWindow(grandChild);
+        root.onResize();
+
+        // Make sure the child does not propagate resize through onParentResize when bounds are set.
+        verify(grandChild, never()).onParentResize();
+
+        child.removeChild(grandChild);
+
+        child.setBounds(null);
+        child.addChildWindow(grandChild);
+        root.onResize();
+
+        // Make sure the child propagates resize through onParentResize when no bounds set.
+        verify(grandChild, times(1)).onParentResize();
+    }
+
+    /* Used so we can gain access to some protected members of the {@link WindowContainer} class */
+    private class TestWindowContainer extends WindowContainer<TestWindowContainer> {
+        private final int mLayer;
+        private boolean mIsAnimating;
+        private boolean mIsVisible;
+        private boolean mFillsParent;
+        private Integer mOrientation;
+
+        private boolean mOnParentSetCalled;
+        private boolean mOnDescendantOverrideCalled;
+
+        /**
+         * Compares 2 window layers and returns -1 if the first is lesser than the second in terms
+         * of z-order and 1 otherwise.
+         */
+        private final Comparator<TestWindowContainer> mWindowSubLayerComparator = (w1, w2) -> {
+            final int layer1 = w1.mLayer;
+            final int layer2 = w2.mLayer;
+            if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
+                // We insert the child window into the list ordered by the mLayer. For same layers,
+                // the negative one should go below others; the positive one should go above others.
+                return -1;
+            }
+            return 1;
+        };
+
+        TestWindowContainer(int layer, boolean isAnimating, boolean isVisible,
+            Integer orientation) {
+            super(sWm);
+            mLayer = layer;
+            mIsAnimating = isAnimating;
+            mIsVisible = isVisible;
+            mFillsParent = true;
+            mOrientation = orientation;
+        }
+
+        TestWindowContainer getParentWindow() {
+            return (TestWindowContainer) getParent();
+        }
+
+        int getChildrenCount() {
+            return mChildren.size();
+        }
+
+        TestWindowContainer addChildWindow(TestWindowContainer child) {
+            addChild(child, mWindowSubLayerComparator);
+            return child;
+        }
+
+        TestWindowContainer addChildWindow(TestWindowContainerBuilder childBuilder) {
+            TestWindowContainer child = childBuilder.build();
+            addChild(child, mWindowSubLayerComparator);
+            return child;
+        }
+
+        TestWindowContainer addChildWindow() {
+            return addChildWindow(new TestWindowContainerBuilder().setLayer(1));
+        }
+
+        @Override
+        void onParentSet() {
+            mOnParentSetCalled = true;
+        }
+
+        @Override
+        void onDescendantOverrideConfigurationChanged() {
+            mOnDescendantOverrideCalled = true;
+            super.onDescendantOverrideConfigurationChanged();
+        }
+
+        @Override
+        boolean isSelfAnimating() {
+            return mIsAnimating;
+        }
+
+        @Override
+        boolean isVisible() {
+            return mIsVisible;
+        }
+
+        @Override
+        int getOrientation(int candidate) {
+            return mOrientation != null ? mOrientation : super.getOrientation(candidate);
+        }
+
+        @Override
+        int getOrientation() {
+            return getOrientation(super.mOrientation);
+        }
+
+        @Override
+        boolean fillsParent() {
+            return mFillsParent;
+        }
+
+        void setFillsParent(boolean fillsParent) {
+            mFillsParent = fillsParent;
+        }
+    }
+
+    private class TestWindowContainerBuilder {
+        private int mLayer;
+        private boolean mIsAnimating;
+        private boolean mIsVisible;
+        private Integer mOrientation;
+
+        public TestWindowContainerBuilder() {
+            reset();
+        }
+
+        TestWindowContainerBuilder setLayer(int layer) {
+            mLayer = layer;
+            return this;
+        }
+
+        TestWindowContainerBuilder setIsAnimating(boolean isAnimating) {
+            mIsAnimating = isAnimating;
+            return this;
+        }
+
+        TestWindowContainerBuilder setIsVisible(boolean isVisible) {
+            mIsVisible = isVisible;
+            return this;
+        }
+
+        TestWindowContainerBuilder setOrientation(int orientation) {
+            mOrientation = orientation;
+            return this;
+        }
+
+        TestWindowContainerBuilder reset() {
+            mLayer = 0;
+            mIsAnimating = false;
+            mIsVisible = false;
+            mOrientation = null;
+            return this;
+        }
+
+        TestWindowContainer build() {
+            return new TestWindowContainer(mLayer, mIsAnimating, mIsVisible, mOrientation);
+        }
+    }
+
+    private class MockSurfaceBuildingContainer extends WindowContainer<WindowContainer> {
+        final SurfaceSession mSession = new SurfaceSession();
+
+        MockSurfaceBuildingContainer() {
+            super(sWm);
+        }
+
+        class MockSurfaceBuilder extends SurfaceControl.Builder {
+            MockSurfaceBuilder(SurfaceSession ss) {
+                super(ss);
+            }
+
+            @Override
+            public SurfaceControl build() {
+                return mock(SurfaceControl.class);
+            }
+        }
+
+        @Override
+        SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+            return new MockSurfaceBuilder(mSession);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java
new file mode 100644
index 0000000..e076399
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -0,0 +1,63 @@
+/*
+ * 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.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Matchers.eq;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.Mock;
+
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link WindowContainer#forAllWindows} and various implementations.
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowContainerTraversalTests extends WindowTestsBase {
+
+    @Test
+    public void testDockedDividerPosition() throws Exception {
+        final WindowState splitScreenWindow = createWindowOnStack(null,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
+                mDisplayContent, "splitScreenWindow");
+        final WindowState splitScreenSecondaryWindow = createWindowOnStack(null,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
+                TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow");
+
+        sWm.mInputMethodTarget = splitScreenWindow;
+
+        Consumer<WindowState> c = mock(Consumer.class);
+        mDisplayContent.forAllWindows(c, false);
+
+        verify(c).accept(eq(mDockedDividerWindow));
+        verify(c).accept(eq(mImeWindow));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
new file mode 100644
index 0000000..5a56332
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) 2016 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 org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.app.ActivityManager.TaskDescription;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.IWindow;
+import android.view.WindowManager;
+
+import static android.view.DisplayCutout.fromBoundingRect;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.FILL_PARENT;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.server.wm.utils.WmDisplayCutout;
+
+/**
+ * Tests for the {@link WindowState#computeFrameLw} method and other window frame machinery.
+ *
+ * Build/Install/Run: bit FrameworksServicesTests:com.android.server.wm.WindowFrameTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowFrameTests extends WindowTestsBase {
+
+    private WindowToken mWindowToken;
+    private final IWindow mIWindow = new TestIWindow();
+
+    class WindowStateWithTask extends WindowState {
+        final Task mTask;
+        boolean mDockedResizingForTest = false;
+        WindowStateWithTask(WindowManager.LayoutParams attrs, Task t) {
+            super(sWm, null, mIWindow, mWindowToken, null, 0, 0, attrs, 0, 0,
+                    false /* ownerCanAddInternalSystemWindow */);
+            mTask = t;
+        }
+
+        @Override
+        Task getTask() {
+            return mTask;
+        }
+
+        @Override
+        boolean isDockedResizing() {
+            return mDockedResizingForTest;
+        }
+    };
+
+    class TaskWithBounds extends Task {
+        final Rect mBounds;
+        final Rect mInsetBounds = new Rect();
+        boolean mFullscreenForTest = true;
+        TaskWithBounds(Rect bounds) {
+            super(0, mStubStack, 0, sWm, 0, false, new TaskDescription(), null);
+            mBounds = bounds;
+            setBounds(bounds);
+        }
+
+        @Override
+        public Rect getBounds() {
+            return mBounds;
+        }
+
+        @Override
+        public void getBounds(Rect out) {
+            out.set(mBounds);
+        }
+
+        @Override
+        public void getOverrideBounds(Rect outBounds) {
+            outBounds.set(mBounds);
+        }
+        @Override
+        void getTempInsetBounds(Rect outBounds) {
+            outBounds.set(mInsetBounds);
+        }
+        @Override
+        boolean isFullscreen() {
+            return mFullscreenForTest;
+        }
+    }
+
+    TaskStack mStubStack;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // Just any non zero value.
+        sWm.mSystemDecorLayer = 10000;
+
+        mWindowToken = WindowTestUtils.createTestAppWindowToken(
+                sWm.getDefaultDisplayContentLocked());
+        mStubStack = new TaskStack(sWm, 0, null);
+    }
+
+    public void assertRect(Rect rect, int left, int top, int right, int bottom) {
+        assertEquals(left, rect.left);
+        assertEquals(top, rect.top);
+        assertEquals(right, rect.right);
+        assertEquals(bottom, rect.bottom);
+    }
+
+    @Test
+    public void testLayoutInFullscreenTaskInsets() throws Exception {
+        Task task = new TaskWithBounds(null); // fullscreen task doesn't use bounds for computeFrame
+        WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT);
+        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
+
+        final int bottomContentInset = 100;
+        final int topContentInset = 50;
+        final int bottomVisibleInset = 30;
+        final int topVisibleInset = 70;
+        final int leftStableInset = 20;
+        final int rightStableInset = 90;
+
+        // With no insets or system decor all the frames incoming from PhoneWindowManager
+        // are identical.
+        final Rect pf = new Rect(0, 0, 1000, 1000);
+        final Rect df = pf;
+        final Rect of = df;
+        final Rect cf = new Rect(pf);
+        // Produce some insets
+        cf.top += 50;
+        cf.bottom -= 100;
+        final Rect vf = new Rect(pf);
+        vf.top += topVisibleInset;
+        vf.bottom -= bottomVisibleInset;
+        final Rect sf = new Rect(pf);
+        sf.left += leftStableInset;
+        sf.right -= rightStableInset;
+
+        final Rect dcf = pf;
+        // When mFrame extends past cf, the content insets are
+        // the difference between mFrame and ContentFrame. Visible
+        // and stable frames work the same way.
+        w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT, false);
+        assertRect(w.mFrame,0, 0, 1000, 1000);
+        assertRect(w.mContentInsets, 0, topContentInset, 0, bottomContentInset);
+        assertRect(w.mVisibleInsets, 0, topVisibleInset, 0, bottomVisibleInset);
+        assertRect(w.mStableInsets, leftStableInset, 0, rightStableInset, 0);
+        // The frames remain as passed in shrunk to the window frame
+        assertTrue(cf.equals(w.getContentFrameLw()));
+        assertTrue(vf.equals(w.getVisibleFrameLw()));
+        assertTrue(sf.equals(w.getStableFrameLw()));
+        // On the other hand mFrame doesn't extend past cf we won't get any insets
+        w.mAttrs.x = 100;
+        w.mAttrs.y = 100;
+        w.mAttrs.width = 100; w.mAttrs.height = 100; //have to clear MATCH_PARENT
+        w.mRequestedWidth = 100;
+        w.mRequestedHeight = 100;
+        w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT, false);
+        assertRect(w.mFrame, 100, 100, 200, 200);
+        assertRect(w.mContentInsets, 0, 0, 0, 0);
+        // In this case the frames are shrunk to the window frame.
+        assertTrue(w.mFrame.equals(w.getContentFrameLw()));
+        assertTrue(w.mFrame.equals(w.getVisibleFrameLw()));
+        assertTrue(w.mFrame.equals(w.getStableFrameLw()));
+    }
+
+    @Test
+    public void testLayoutInFullscreenTaskNoInsets() throws Exception {
+        Task task = new TaskWithBounds(null); // fullscreen task doesn't use bounds for computeFrame
+        WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT);
+        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
+
+        // With no insets or system decor all the frames incoming from PhoneWindowManager
+        // are identical.
+        final Rect pf = new Rect(0, 0, 1000, 1000);
+
+        // Here the window has FILL_PARENT, FILL_PARENT
+        // so we expect it to fill the entire available frame.
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false);
+        assertRect(w.mFrame, 0, 0, 1000, 1000);
+
+        // It can select various widths and heights within the bounds.
+        // Strangely the window attribute width is ignored for normal windows
+        // and we use mRequestedWidth/mRequestedHeight
+        w.mAttrs.width = 300;
+        w.mAttrs.height = 300;
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false);
+        // Explicit width and height without requested width/height
+        // gets us nothing.
+        assertRect(w.mFrame, 0, 0, 0, 0);
+
+        w.mRequestedWidth = 300;
+        w.mRequestedHeight = 300;
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false);
+        // With requestedWidth/Height we can freely choose our size within the
+        // parent bounds.
+        assertRect(w.mFrame, 0, 0, 300, 300);
+
+        // With FLAG_SCALED though, requestedWidth/height is used to control
+        // the unscaled surface size, and mAttrs.width/height becomes the
+        // layout controller.
+        w.mAttrs.flags = WindowManager.LayoutParams.FLAG_SCALED;
+        w.mRequestedHeight = -1;
+        w.mRequestedWidth = -1;
+        w.mAttrs.width = 100;
+        w.mAttrs.height = 100;
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false);
+        assertRect(w.mFrame, 0, 0, 100, 100);
+        w.mAttrs.flags = 0;
+
+        // But sizes too large will be clipped to the containing frame
+        w.mRequestedWidth = 1200;
+        w.mRequestedHeight = 1200;
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false);
+        assertRect(w.mFrame, 0, 0, 1000, 1000);
+
+        // Before they are clipped though windows will be shifted
+        w.mAttrs.x = 300;
+        w.mAttrs.y = 300;
+        w.mRequestedWidth = 1000;
+        w.mRequestedHeight = 1000;
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false);
+        assertRect(w.mFrame, 0, 0, 1000, 1000);
+
+        // If there is room to move around in the parent frame the window will be shifted according
+        // to gravity.
+        w.mAttrs.x = 0;
+        w.mAttrs.y = 0;
+        w.mRequestedWidth = 300;
+        w.mRequestedHeight = 300;
+        w.mAttrs.gravity = Gravity.RIGHT | Gravity.TOP;
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false);
+        assertRect(w.mFrame, 700, 0, 1000, 300);
+        w.mAttrs.gravity = Gravity.RIGHT | Gravity.BOTTOM;
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false);
+        assertRect(w.mFrame, 700, 700, 1000, 1000);
+        // Window specified  x and y are interpreted as offsets in the opposite
+        // direction of gravity
+        w.mAttrs.x = 100;
+        w.mAttrs.y = 100;
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false);
+        assertRect(w.mFrame, 600, 600, 900, 900);
+    }
+
+    @Test
+    public void testLayoutNonfullscreenTask() {
+        final DisplayInfo displayInfo = sWm.getDefaultDisplayContentLocked().getDisplayInfo();
+        final int logicalWidth = displayInfo.logicalWidth;
+        final int logicalHeight = displayInfo.logicalHeight;
+
+        final int taskLeft = logicalWidth / 4;
+        final int taskTop = logicalHeight / 4;
+        final int taskRight = logicalWidth / 4 * 3;
+        final int taskBottom = logicalHeight / 4 * 3;
+        final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom);
+        TaskWithBounds task = new TaskWithBounds(taskBounds);
+        task.mFullscreenForTest = false;
+        WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT);
+        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
+
+        final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, null, WmDisplayCutout.NO_CUTOUT, false);
+        // For non fullscreen tasks the containing frame is based off the
+        // task bounds not the parent frame.
+        assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom);
+        assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight, taskBottom);
+        assertRect(w.mContentInsets, 0, 0, 0, 0);
+
+        pf.set(0, 0, logicalWidth, logicalHeight);
+        // We still produce insets against the containing frame the same way.
+        final int cfRight = logicalWidth / 2;
+        final int cfBottom = logicalHeight / 2;
+        final Rect cf = new Rect(0, 0, cfRight, cfBottom);
+        w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, WmDisplayCutout.NO_CUTOUT, false);
+        assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom);
+        int contentInsetRight = taskRight - cfRight;
+        int contentInsetBottom = taskBottom - cfBottom;
+        assertRect(w.mContentInsets, 0, 0, contentInsetRight, contentInsetBottom);
+        assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight - contentInsetRight,
+                taskBottom - contentInsetBottom);
+
+        pf.set(0, 0, logicalWidth, logicalHeight);
+        // However if we set temp inset bounds, the insets will be computed
+        // as if our window was laid out there,  but it will be laid out according to
+        // the task bounds.
+        final int insetLeft = logicalWidth / 5;
+        final int insetTop = logicalHeight / 5;
+        final int insetRight = insetLeft + (taskRight - taskLeft);
+        final int insetBottom = insetTop + (taskBottom - taskTop);
+        task.mInsetBounds.set(insetLeft, insetTop, insetRight, insetBottom);
+        w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, WmDisplayCutout.NO_CUTOUT, false);
+        assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom);
+        contentInsetRight = insetRight - cfRight;
+        contentInsetBottom = insetBottom - cfBottom;
+        assertRect(w.mContentInsets, 0, 0, contentInsetRight, contentInsetBottom);
+        assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight - contentInsetRight,
+                taskBottom - contentInsetBottom);
+    }
+
+    @Test
+    public void testCalculatePolicyCrop() {
+        final WindowStateWithTask w = createWindow(
+                new TaskWithBounds(null), FILL_PARENT, FILL_PARENT);
+        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
+
+        final DisplayInfo displayInfo = w.getDisplayContent().getDisplayInfo();
+        final int logicalWidth = displayInfo.logicalWidth;
+        final int logicalHeight = displayInfo.logicalHeight;
+        final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
+        final Rect df = pf;
+        final Rect of = df;
+        final Rect cf = new Rect(pf);
+        // Produce some insets
+        cf.top += displayInfo.logicalWidth / 10;
+        cf.bottom -= displayInfo.logicalWidth / 5;
+        final Rect vf = cf;
+        final Rect sf = vf;
+        // We use a decor content frame with insets to produce cropping.
+        Rect dcf = cf;
+
+        final Rect policyCrop = new Rect();
+
+        w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT, false);
+        w.calculatePolicyCrop(policyCrop);
+        assertRect(policyCrop, 0, cf.top, logicalWidth, cf.bottom);
+
+        dcf.setEmpty();
+        // Likewise with no decor frame we would get no crop
+        w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT, false);
+        w.calculatePolicyCrop(policyCrop);
+        assertRect(policyCrop, 0, 0, logicalWidth, logicalHeight);
+
+        // Now we set up a window which doesn't fill the entire decor frame.
+        // Normally it would be cropped to it's frame but in the case of docked resizing
+        // we need to account for the fact the windows surface will be made
+        // fullscreen and thus also make the crop fullscreen.
+        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
+        w.mAttrs.width = logicalWidth / 2;
+        w.mAttrs.height = logicalHeight / 2;
+        w.mRequestedWidth = logicalWidth / 2;
+        w.mRequestedHeight = logicalHeight / 2;
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false);
+
+        w.calculatePolicyCrop(policyCrop);
+        // Normally the crop is shrunk from the decor frame
+        // to the computed window frame.
+        assertRect(policyCrop, 0, 0, logicalWidth / 2, logicalHeight / 2);
+
+        w.mDockedResizingForTest = true;
+        w.calculatePolicyCrop(policyCrop);
+        // But if we are docked resizing it won't be, however we will still be
+        // shrunk to the decor frame and the display.
+        assertRect(policyCrop, 0, 0,
+                Math.min(pf.width(), displayInfo.logicalWidth),
+                Math.min(pf.height(), displayInfo.logicalHeight));
+    }
+
+    @Test
+    public void testLayoutLetterboxedWindow() {
+        // First verify task behavior in multi-window mode.
+        final DisplayInfo displayInfo = sWm.getDefaultDisplayContentLocked().getDisplayInfo();
+        final int logicalWidth = displayInfo.logicalWidth;
+        final int logicalHeight = displayInfo.logicalHeight;
+
+        final int taskLeft = logicalWidth / 5;
+        final int taskTop = logicalHeight / 5;
+        final int taskRight = logicalWidth / 4 * 3;
+        final int taskBottom = logicalHeight / 4 * 3;
+        final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom);
+        TaskWithBounds task = new TaskWithBounds(taskBounds);
+        task.mInsetBounds.set(taskLeft, taskTop, taskRight, taskBottom);
+        task.mFullscreenForTest = false;
+        WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT);
+        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
+
+        final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
+        w.computeFrameLw(pf /* parentFrame */, pf /* displayFrame */, pf /* overscanFrame */,
+                pf /* contentFrame */, pf /* visibleFrame */, pf /* decorFrame */,
+                pf /* stableFrame */, null /* outsetFrame */, WmDisplayCutout.NO_CUTOUT, false);
+        // For non fullscreen tasks the containing frame is based off the
+        // task bounds not the parent frame.
+        assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom);
+        assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight, taskBottom);
+        assertRect(w.mContentInsets, 0, 0, 0, 0);
+
+        // Now simulate switch to fullscreen for letterboxed app.
+        final int xInset = logicalWidth / 10;
+        final int yInset = logicalWidth / 10;
+        final Rect cf = new Rect(xInset, yInset, logicalWidth - xInset, logicalHeight - yInset);
+        Configuration config = new Configuration(w.mAppToken.getOverrideConfiguration());
+        config.windowConfiguration.setBounds(cf);
+        w.mAppToken.onOverrideConfigurationChanged(config);
+        pf.set(0, 0, logicalWidth, logicalHeight);
+        task.mFullscreenForTest = true;
+
+        w.computeFrameLw(pf /* parentFrame */, pf /* displayFrame */, pf /* overscanFrame */,
+                cf /* contentFrame */, cf /* visibleFrame */, pf /* decorFrame */,
+                cf /* stableFrame */, null /* outsetFrame */, WmDisplayCutout.NO_CUTOUT, false);
+        assertEquals(cf, w.mFrame);
+        assertEquals(cf, w.getContentFrameLw());
+        assertRect(w.mContentInsets, 0, 0, 0, 0);
+    }
+
+    @Test
+    public void testDisplayCutout() {
+        // Regular fullscreen task and window
+        Task task = new TaskWithBounds(null);
+        WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT);
+        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
+
+        final Rect pf = new Rect(0, 0, 1000, 2000);
+        // Create a display cutout of size 50x50, aligned top-center
+        final WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(500, 0, 550, 50), pf.width(), pf.height());
+
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, cutout, false);
+
+        assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetTop(), 50);
+        assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetBottom(), 0);
+        assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetLeft(), 0);
+        assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetRight(), 0);
+    }
+
+    @Test
+    public void testDisplayCutout_tempInsetBounds() {
+        // Regular fullscreen task and window
+        TaskWithBounds task = new TaskWithBounds(new Rect(0, -500, 1000, 1500));
+        task.mFullscreenForTest = false;
+        task.mInsetBounds.set(0, 0, 1000, 2000);
+        WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT);
+        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
+
+        final Rect pf = new Rect(0, -500, 1000, 1500);
+        // Create a display cutout of size 50x50, aligned top-center
+        final WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(500, 0, 550, 50), pf.width(), pf.height());
+
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, cutout, false);
+
+        assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetTop(), 50);
+        assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetBottom(), 0);
+        assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetLeft(), 0);
+        assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetRight(), 0);
+    }
+
+    private WindowStateWithTask createWindow(Task task, int width, int height) {
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
+        attrs.width = width;
+        attrs.height = height;
+
+        return new WindowStateWithTask(attrs, task);
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRule.java b/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRule.java
new file mode 100644
index 0000000..d91079e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRule.java
@@ -0,0 +1,157 @@
+/*
+ * 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.wm;
+
+import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
+import android.support.test.InstrumentationRegistry;
+import android.view.InputChannel;
+
+import com.android.server.LocalServices;
+import com.android.server.input.InputManagerService;
+import com.android.server.policy.WindowManagerPolicy;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.invocation.InvocationOnMock;
+
+/**
+ * A test rule that sets up a fresh WindowManagerService instance before each test and makes sure
+ * to properly tear it down after.
+ *
+ * <p>
+ * Usage:
+ * <pre>
+ * {@literal @}Rule
+ *  public final WindowManagerServiceRule mWmRule = new WindowManagerServiceRule();
+ * </pre>
+ */
+public class WindowManagerServiceRule implements TestRule {
+
+    private WindowManagerService mService;
+    private TestWindowManagerPolicy mPolicy;
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                runWithDexmakerShareClassLoader(this::setUp);
+                try {
+                    base.evaluate();
+                } finally {
+                    tearDown();
+                }
+            }
+
+            private void setUp() {
+                final Context context = InstrumentationRegistry.getTargetContext();
+
+                removeServices();
+
+                LocalServices.addService(DisplayManagerInternal.class,
+                        mock(DisplayManagerInternal.class));
+
+                LocalServices.addService(PowerManagerInternal.class,
+                        mock(PowerManagerInternal.class));
+                final PowerManagerInternal pm =
+                        LocalServices.getService(PowerManagerInternal.class);
+                PowerSaveState state = new PowerSaveState.Builder().build();
+                doReturn(state).when(pm).getLowPowerState(anyInt());
+
+                LocalServices.addService(ActivityManagerInternal.class,
+                        mock(ActivityManagerInternal.class));
+                final ActivityManagerInternal am =
+                        LocalServices.getService(ActivityManagerInternal.class);
+                doAnswer((InvocationOnMock invocationOnMock) -> {
+                    final Runnable runnable = invocationOnMock.<Runnable>getArgument(0);
+                    if (runnable != null) {
+                        runnable.run();
+                    }
+                    return null;
+                }).when(am).notifyKeyguardFlagsChanged(any());
+
+                InputManagerService ims = mock(InputManagerService.class);
+                // InputChannel is final and can't be mocked.
+                InputChannel[] input = InputChannel.openInputChannelPair(TAG_WM);
+                if (input != null && input.length > 1) {
+                    doReturn(input[1]).when(ims).monitorInput(anyString());
+                }
+
+                mService = WindowManagerService.main(context, ims, true, false,
+                        false, mPolicy = new TestWindowManagerPolicy(
+                                WindowManagerServiceRule.this::getWindowManagerService));
+
+                mService.onInitReady();
+
+                // Display creation is driven by the ActivityManagerService via ActivityStackSupervisor.
+                // We emulate those steps here.
+                mService.mRoot.createDisplayContent(
+                        mService.mDisplayManager.getDisplay(DEFAULT_DISPLAY),
+                        mock(DisplayWindowController.class));
+            }
+
+            private void removeServices() {
+                LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+                LocalServices.removeServiceForTest(PowerManagerInternal.class);
+                LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+                LocalServices.removeServiceForTest(WindowManagerInternal.class);
+                LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+            }
+
+            private void tearDown() {
+                waitUntilWindowManagerHandlersIdle();
+                removeServices();
+                mService = null;
+                mPolicy = null;
+            }
+        };
+    }
+
+    public WindowManagerService getWindowManagerService() {
+        return mService;
+    }
+
+    public TestWindowManagerPolicy getWindowManagerPolicy() {
+        return mPolicy;
+    }
+
+    public void waitUntilWindowManagerHandlersIdle() {
+        final WindowManagerService wm = getWindowManagerService();
+        if (wm != null) {
+            wm.mH.runWithScissors(() -> { }, 0);
+            wm.mAnimationHandler.runWithScissors(() -> { }, 0);
+            SurfaceAnimationThread.getHandler().runWithScissors(() -> { }, 0);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRuleTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRuleTest.java
new file mode 100644
index 0000000..6cf6d7b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRuleTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.wm;
+
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+@SmallTest
+public class WindowManagerServiceRuleTest {
+
+    @Rule
+    public final WindowManagerServiceRule mRule = new WindowManagerServiceRule();
+
+    @Test
+    public void testWindowManagerSetUp() {
+        assertThat(mRule.getWindowManagerService(), notNullValue());
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
new file mode 100644
index 0000000..85e846d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2016 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 android.view.WindowManager;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.util.LinkedList;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for the {@link WindowState} class.
+ *
+ * atest FrameworksServicesTests:com.android.server.wm.WindowStateTests
+ */
+@SmallTest
+@FlakyTest(bugId = 74078662)
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowStateTests extends WindowTestsBase {
+
+    @Test
+    public void testIsParentWindowHidden() throws Exception {
+        final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1");
+        final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2");
+
+        // parentWindow is initially set to hidden.
+        assertTrue(parentWindow.mHidden);
+        assertFalse(parentWindow.isParentWindowHidden());
+        assertTrue(child1.isParentWindowHidden());
+        assertTrue(child2.isParentWindowHidden());
+
+        parentWindow.mHidden = false;
+        assertFalse(parentWindow.isParentWindowHidden());
+        assertFalse(child1.isParentWindowHidden());
+        assertFalse(child2.isParentWindowHidden());
+
+    }
+
+    @Test
+    public void testIsChildWindow() throws Exception {
+        final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1");
+        final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2");
+        final WindowState randomWindow = createWindow(null, TYPE_APPLICATION, "randomWindow");
+
+        assertFalse(parentWindow.isChildWindow());
+        assertTrue(child1.isChildWindow());
+        assertTrue(child2.isChildWindow());
+        assertFalse(randomWindow.isChildWindow());
+    }
+
+    @Test
+    public void testHasChild() throws Exception {
+        final WindowState win1 = createWindow(null, TYPE_APPLICATION, "win1");
+        final WindowState win11 = createWindow(win1, FIRST_SUB_WINDOW, "win11");
+        final WindowState win12 = createWindow(win1, FIRST_SUB_WINDOW, "win12");
+        final WindowState win2 = createWindow(null, TYPE_APPLICATION, "win2");
+        final WindowState win21 = createWindow(win2, FIRST_SUB_WINDOW, "win21");
+        final WindowState randomWindow = createWindow(null, TYPE_APPLICATION, "randomWindow");
+
+        assertTrue(win1.hasChild(win11));
+        assertTrue(win1.hasChild(win12));
+        assertTrue(win2.hasChild(win21));
+
+        assertFalse(win1.hasChild(win21));
+        assertFalse(win1.hasChild(randomWindow));
+
+        assertFalse(win2.hasChild(win11));
+        assertFalse(win2.hasChild(win12));
+        assertFalse(win2.hasChild(randomWindow));
+    }
+
+    @Test
+    public void testGetParentWindow() throws Exception {
+        final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1");
+        final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2");
+
+        assertNull(parentWindow.getParentWindow());
+        assertEquals(parentWindow, child1.getParentWindow());
+        assertEquals(parentWindow, child2.getParentWindow());
+    }
+
+    @Test
+    public void testOverlayWindowHiddenWhenSuspended() {
+        final WindowState overlayWindow = spy(createWindow(null, TYPE_APPLICATION_OVERLAY,
+                "overlayWindow"));
+        overlayWindow.setHiddenWhileSuspended(true);
+        verify(overlayWindow).hideLw(true, true);
+        overlayWindow.setHiddenWhileSuspended(false);
+        verify(overlayWindow).showLw(true, true);
+    }
+
+    @Test
+    public void testGetTopParentWindow() throws Exception {
+        final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
+        final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1");
+        final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2");
+
+        assertEquals(root, root.getTopParentWindow());
+        assertEquals(root, child1.getTopParentWindow());
+        assertEquals(child1, child2.getParentWindow());
+        assertEquals(root, child2.getTopParentWindow());
+
+        // Test case were child is detached from parent.
+        root.removeChild(child1);
+        assertEquals(child1, child1.getTopParentWindow());
+        assertEquals(child1, child2.getParentWindow());
+    }
+
+    @Test
+    public void testIsOnScreen_hiddenByPolicy() {
+        final WindowState window = createWindow(null, TYPE_APPLICATION, "window");
+        window.setHasSurface(true);
+        assertTrue(window.isOnScreen());
+        window.hideLw(false /* doAnimation */);
+        assertFalse(window.isOnScreen());
+    }
+
+    @Test
+    public void testCanBeImeTarget() throws Exception {
+        final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+        final WindowState imeWindow = createWindow(null, TYPE_INPUT_METHOD, "imeWindow");
+
+        // Setting FLAG_NOT_FOCUSABLE without FLAG_ALT_FOCUSABLE_IM prevents the window from being
+        // an IME target.
+        appWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+        imeWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+
+        // Make windows visible
+        appWindow.setHasSurface(true);
+        imeWindow.setHasSurface(true);
+
+        // Windows without flags (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM) can't be IME targets
+        assertFalse(appWindow.canBeImeTarget());
+        assertFalse(imeWindow.canBeImeTarget());
+
+        // Add IME target flags
+        appWindow.mAttrs.flags |= (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
+        imeWindow.mAttrs.flags |= (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
+
+        // Visible app window with flags can be IME target while an IME window can never be an IME
+        // target regardless of its visibility or flags.
+        assertTrue(appWindow.canBeImeTarget());
+        assertFalse(imeWindow.canBeImeTarget());
+
+        // Make windows invisible
+        appWindow.hideLw(false /* doAnimation */);
+        imeWindow.hideLw(false /* doAnimation */);
+
+        // Invisible window can't be IME targets even if they have the right flags.
+        assertFalse(appWindow.canBeImeTarget());
+        assertFalse(imeWindow.canBeImeTarget());
+    }
+
+    @Test
+    public void testGetWindow() throws Exception {
+        final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
+        final WindowState mediaChild = createWindow(root, TYPE_APPLICATION_MEDIA, "mediaChild");
+        final WindowState mediaOverlayChild = createWindow(root,
+                TYPE_APPLICATION_MEDIA_OVERLAY, "mediaOverlayChild");
+        final WindowState attachedDialogChild = createWindow(root,
+                TYPE_APPLICATION_ATTACHED_DIALOG, "attachedDialogChild");
+        final WindowState subPanelChild = createWindow(root,
+                TYPE_APPLICATION_SUB_PANEL, "subPanelChild");
+        final WindowState aboveSubPanelChild = createWindow(root,
+                TYPE_APPLICATION_ABOVE_SUB_PANEL, "aboveSubPanelChild");
+
+        final LinkedList<WindowState> windows = new LinkedList();
+
+        root.getWindow(w -> {
+            windows.addLast(w);
+            return false;
+        });
+
+        // getWindow should have returned candidate windows in z-order.
+        assertEquals(aboveSubPanelChild, windows.pollFirst());
+        assertEquals(subPanelChild, windows.pollFirst());
+        assertEquals(attachedDialogChild, windows.pollFirst());
+        assertEquals(root, windows.pollFirst());
+        assertEquals(mediaOverlayChild, windows.pollFirst());
+        assertEquals(mediaChild, windows.pollFirst());
+        assertTrue(windows.isEmpty());
+    }
+
+    @Test
+    public void testPrepareWindowToDisplayDuringRelayout() throws Exception {
+        testPrepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
+        testPrepareWindowToDisplayDuringRelayout(true /*wasVisible*/);
+
+        // Call prepareWindowToDisplayDuringRelayout for a window without FLAG_TURN_SCREEN_ON
+        // before calling prepareWindowToDisplayDuringRelayout for windows with flag in the same
+        // appWindowToken.
+        final AppWindowToken appWindowToken = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState first = createWindow(null, TYPE_APPLICATION, appWindowToken, "first");
+        final WindowState second = createWindow(null, TYPE_APPLICATION, appWindowToken, "second");
+        second.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
+
+        reset(mPowerManagerWrapper);
+        first.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
+        verify(mPowerManagerWrapper, never()).wakeUp(anyLong(), anyString());
+        assertTrue(appWindowToken.canTurnScreenOn());
+
+        reset(mPowerManagerWrapper);
+        second.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
+        verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString());
+        assertFalse(appWindowToken.canTurnScreenOn());
+
+        // Call prepareWindowToDisplayDuringRelayout for two window that have FLAG_TURN_SCREEN_ON
+        // from the same appWindowToken. Only one should trigger the wakeup.
+        appWindowToken.setCanTurnScreenOn(true);
+        first.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
+        second.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
+
+        reset(mPowerManagerWrapper);
+        first.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
+        verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString());
+        assertFalse(appWindowToken.canTurnScreenOn());
+
+        reset(mPowerManagerWrapper);
+        second.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
+        verify(mPowerManagerWrapper, never()).wakeUp(anyLong(), anyString());
+        assertFalse(appWindowToken.canTurnScreenOn());
+
+        // Call prepareWindowToDisplayDuringRelayout for a windows that are not children of an
+        // appWindowToken. Both windows have the FLAG_TURNS_SCREEN_ON so both should call wakeup
+        final WindowToken windowToken = WindowTestUtils.createTestWindowToken(FIRST_SUB_WINDOW,
+                mDisplayContent);
+        final WindowState firstWindow = createWindow(null, TYPE_APPLICATION, windowToken,
+                "firstWindow");
+        final WindowState secondWindow = createWindow(null, TYPE_APPLICATION, windowToken,
+                "secondWindow");
+        firstWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
+        secondWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
+
+        reset(mPowerManagerWrapper);
+        firstWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
+        verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString());
+
+        reset(mPowerManagerWrapper);
+        secondWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
+        verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString());
+    }
+
+    @Test
+    public void testCanAffectSystemUiFlags() throws Exception {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        app.mToken.setHidden(false);
+        assertTrue(app.canAffectSystemUiFlags());
+        app.mToken.setHidden(true);
+        assertFalse(app.canAffectSystemUiFlags());
+        app.mToken.setHidden(false);
+        app.mAttrs.alpha = 0.0f;
+        assertFalse(app.canAffectSystemUiFlags());
+
+    }
+
+    @Test
+    public void testCanAffectSystemUiFlags_disallow() throws Exception {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        app.mToken.setHidden(false);
+        assertTrue(app.canAffectSystemUiFlags());
+        app.getTask().setCanAffectSystemUiFlags(false);
+        assertFalse(app.canAffectSystemUiFlags());
+    }
+
+    @Test
+    public void testIsSelfOrAncestorWindowAnimating() throws Exception {
+        final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
+        final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1");
+        final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2");
+        assertFalse(child2.isSelfOrAncestorWindowAnimatingExit());
+        child2.mAnimatingExit = true;
+        assertTrue(child2.isSelfOrAncestorWindowAnimatingExit());
+        child2.mAnimatingExit = false;
+        root.mAnimatingExit = true;
+        assertTrue(child2.isSelfOrAncestorWindowAnimatingExit());
+    }
+
+    @Test
+    public void testLayoutSeqResetOnReparent() throws Exception {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        app.mLayoutSeq = 1;
+        mDisplayContent.mLayoutSeq = 1;
+
+        app.onDisplayChanged(mDisplayContent);
+
+        assertThat(app.mLayoutSeq, not(is(mDisplayContent.mLayoutSeq)));
+    }
+
+    @Test
+    public void testDisplayIdUpdatedOnReparent() throws Exception {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        // fake a different display
+        app.mInputWindowHandle.displayId = mDisplayContent.getDisplayId() + 1;
+        app.onDisplayChanged(mDisplayContent);
+
+        assertThat(app.mInputWindowHandle.displayId, is(mDisplayContent.getDisplayId()));
+        assertThat(app.getDisplayId(), is(mDisplayContent.getDisplayId()));
+    }
+
+    private void testPrepareWindowToDisplayDuringRelayout(boolean wasVisible) {
+        reset(mPowerManagerWrapper);
+        final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
+        root.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
+
+        root.prepareWindowToDisplayDuringRelayout(wasVisible /*wasVisible*/);
+        verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowSurfacePlacerTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowSurfacePlacerTest.java
new file mode 100644
index 0000000..e173b7d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowSurfacePlacerTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN;
+import static junit.framework.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.WindowManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowSurfacePlacerTest extends WindowTestsBase {
+
+    private WindowSurfacePlacer mWindowSurfacePlacer;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mWindowSurfacePlacer = new WindowSurfacePlacer(sWm);
+    }
+
+    @Test
+    public void testTranslucentOpen() throws Exception {
+        synchronized (sWm.mWindowMap) {
+            final AppWindowToken behind = createAppWindowToken(mDisplayContent,
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+            final AppWindowToken translucentOpening = createAppWindowToken(mDisplayContent,
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+            translucentOpening.setFillsParent(false);
+            translucentOpening.setHidden(true);
+            sWm.mOpeningApps.add(behind);
+            sWm.mOpeningApps.add(translucentOpening);
+            assertEquals(WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_OPEN,
+                    mWindowSurfacePlacer.maybeUpdateTransitToTranslucentAnim(TRANSIT_TASK_OPEN));
+        }
+    }
+
+    @Test
+    public void testTranslucentClose() throws Exception {
+        synchronized (sWm.mWindowMap) {
+            final AppWindowToken behind = createAppWindowToken(mDisplayContent,
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+            final AppWindowToken translucentClosing = createAppWindowToken(mDisplayContent,
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+            translucentClosing.setFillsParent(false);
+            sWm.mClosingApps.add(translucentClosing);
+            assertEquals(WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE,
+                    mWindowSurfacePlacer.maybeUpdateTransitToTranslucentAnim(TRANSIT_TASK_CLOSE));
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
new file mode 100644
index 0000000..2e4740b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2017 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 android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.IApplicationToken;
+import android.view.IWindow;
+import android.view.WindowManager;
+
+import static android.app.AppOpsManager.OP_NONE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.mockito.invocation.InvocationOnMock;
+
+/**
+ * A collection of static functions that can be referenced by other test packages to provide access
+ * to WindowManager related test functionality.
+ */
+public class WindowTestUtils {
+    public static int sNextTaskId = 0;
+
+    /**
+     * Retrieves an instance of a mock {@link WindowManagerService}.
+     */
+    public static WindowManagerService getMockWindowManagerService() {
+        final WindowManagerService service = mock(WindowManagerService.class);
+        final WindowHashMap windowMap = new WindowHashMap();
+        when(service.getWindowManagerLock()).thenReturn(windowMap);
+        return service;
+    }
+
+    /**
+     * Creates a mock instance of {@link StackWindowController}.
+     */
+    public static StackWindowController createMockStackWindowContainerController() {
+        StackWindowController controller = mock(StackWindowController.class);
+        controller.mContainer = mock(TestTaskStack.class);
+
+        // many components rely on the {@link StackWindowController#adjustConfigurationForBounds}
+        // to properly set bounds values in the configuration. We must mimick those actions here.
+        doAnswer((InvocationOnMock invocationOnMock) -> {
+            final Configuration config = invocationOnMock.<Configuration>getArgument(7);
+            final Rect bounds = invocationOnMock.<Rect>getArgument(0);
+            config.windowConfiguration.setBounds(bounds);
+            return null;
+        }).when(controller).adjustConfigurationForBounds(any(), any(), any(), any(),
+                anyBoolean(), anyBoolean(), anyFloat(), any(), any(), anyInt());
+
+        return controller;
+    }
+
+    /** Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
+    public static Task createTaskInStack(WindowManagerService service, TaskStack stack,
+            int userId) {
+        synchronized (service.mWindowMap) {
+            final Task newTask = new Task(sNextTaskId++, stack, userId, service, 0, false,
+                    new ActivityManager.TaskDescription(), null);
+            stack.addTask(newTask, POSITION_TOP);
+            return newTask;
+        }
+    }
+
+    /**
+     * An extension of {@link TestTaskStack}, which overrides package scoped methods that would not
+     * normally be mocked out.
+     */
+    public static class TestTaskStack extends TaskStack {
+        TestTaskStack(WindowManagerService service, int stackId) {
+            super(service, stackId, null);
+        }
+
+        @Override
+        void addTask(Task task, int position, boolean showForAllUsers, boolean moveParents) {
+            // Do nothing.
+        }
+    }
+
+    static TestAppWindowToken createTestAppWindowToken(DisplayContent dc) {
+        synchronized (dc.mService.mWindowMap) {
+            return new TestAppWindowToken(dc);
+        }
+    }
+
+    /** Used so we can gain access to some protected members of the {@link AppWindowToken} class. */
+    public static class TestAppWindowToken extends AppWindowToken {
+        boolean mOnTop = false;
+
+        private TestAppWindowToken(DisplayContent dc) {
+            super(dc.mService, new IApplicationToken.Stub() {
+                public String getName() {return null;}
+                }, false, dc, true /* fillsParent */);
+        }
+
+        TestAppWindowToken(WindowManagerService service, IApplicationToken token,
+                boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
+                boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
+                int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
+                boolean alwaysFocusable, AppWindowContainerController controller) {
+            super(service, token, voiceInteraction, dc, inputDispatchingTimeoutNanos, fullscreen,
+                    showForAllUsers, targetSdk, orientation, rotationAnimationHint, configChanges,
+                    launchTaskBehind, alwaysFocusable, controller);
+        }
+
+        int getWindowsCount() {
+            return mChildren.size();
+        }
+
+        boolean hasWindow(WindowState w) {
+            return mChildren.contains(w);
+        }
+
+        WindowState getFirstChild() {
+            return mChildren.peekFirst();
+        }
+
+        WindowState getLastChild() {
+            return mChildren.peekLast();
+        }
+
+        int positionInParent() {
+            return getParent().mChildren.indexOf(this);
+        }
+
+        void setIsOnTop(boolean onTop) {
+            mOnTop = onTop;
+        }
+
+        @Override
+        boolean isOnTop() {
+            return mOnTop;
+        }
+    }
+
+    static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
+        return createTestWindowToken(type, dc, false /* persistOnEmpty */);
+    }
+
+    static TestWindowToken createTestWindowToken(int type, DisplayContent dc,
+            boolean persistOnEmpty) {
+        synchronized (dc.mService.mWindowMap) {
+            return new TestWindowToken(type, dc, persistOnEmpty);
+        }
+    }
+
+    /* Used so we can gain access to some protected members of the {@link WindowToken} class */
+    public static class TestWindowToken extends WindowToken {
+
+        private TestWindowToken(int type, DisplayContent dc, boolean persistOnEmpty) {
+            super(dc.mService, mock(IBinder.class), type, persistOnEmpty, dc,
+                    false /* ownerCanManageAppTokens */);
+        }
+
+        int getWindowsCount() {
+            return mChildren.size();
+        }
+
+        boolean hasWindow(WindowState w) {
+            return mChildren.contains(w);
+        }
+    }
+
+    /* Used so we can gain access to some protected members of the {@link Task} class */
+    public static class TestTask extends Task {
+        boolean mShouldDeferRemoval = false;
+        boolean mOnDisplayChangedCalled = false;
+        private boolean mIsAnimating = false;
+
+        TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service,
+                int resizeMode, boolean supportsPictureInPicture,
+                TaskWindowContainerController controller) {
+            super(taskId, stack, userId, service, resizeMode, supportsPictureInPicture,
+                    new ActivityManager.TaskDescription(), controller);
+        }
+
+        boolean shouldDeferRemoval() {
+            return mShouldDeferRemoval;
+        }
+
+        int positionInParent() {
+            return getParent().mChildren.indexOf(this);
+        }
+
+        @Override
+        void onDisplayChanged(DisplayContent dc) {
+            super.onDisplayChanged(dc);
+            mOnDisplayChangedCalled = true;
+        }
+
+        @Override
+        boolean isSelfAnimating() {
+            return mIsAnimating;
+        }
+
+        void setLocalIsAnimating(boolean isAnimating) {
+            mIsAnimating = isAnimating;
+        }
+    }
+
+    /**
+     * Used so we can gain access to some protected members of {@link TaskWindowContainerController}
+     * class.
+     */
+    public static class TestTaskWindowContainerController extends TaskWindowContainerController {
+
+        TestTaskWindowContainerController(WindowTestsBase testsBase) {
+            this(testsBase.createStackControllerOnDisplay(testsBase.mDisplayContent));
+        }
+
+        TestTaskWindowContainerController(StackWindowController stackController) {
+            super(sNextTaskId++, new TaskWindowContainerListener() {
+                        @Override
+                        public void registerConfigurationChangeListener(
+                                ConfigurationContainerListener listener) {
+
+                        }
+
+                        @Override
+                        public void unregisterConfigurationChangeListener(
+                                ConfigurationContainerListener listener) {
+
+                        }
+
+                        @Override
+                        public void onSnapshotChanged(ActivityManager.TaskSnapshot snapshot) {
+
+                        }
+
+                        @Override
+                        public void requestResize(Rect bounds, int resizeMode) {
+
+                        }
+                    }, stackController, 0 /* userId */, null /* bounds */, RESIZE_MODE_UNRESIZEABLE,
+                    false /* supportsPictureInPicture */, true /* toTop*/,
+                    true /* showForAllUsers */, new ActivityManager.TaskDescription(),
+                    stackController.mService);
+        }
+
+        @Override
+        TestTask createTask(int taskId, TaskStack stack, int userId, int resizeMode,
+                boolean supportsPictureInPicture, ActivityManager.TaskDescription taskDescription) {
+            return new TestTask(taskId, stack, userId, mService, resizeMode,
+                    supportsPictureInPicture, this);
+        }
+    }
+
+    public static class TestAppWindowContainerController extends AppWindowContainerController {
+
+        final IApplicationToken mToken;
+
+        TestAppWindowContainerController(TestTaskWindowContainerController taskController) {
+            this(taskController, new TestIApplicationToken());
+        }
+
+        TestAppWindowContainerController(TestTaskWindowContainerController taskController,
+                IApplicationToken token) {
+            super(taskController, token, null /* listener */, 0 /* index */,
+                    SCREEN_ORIENTATION_UNSPECIFIED, true /* fullscreen */,
+                    true /* showForAllUsers */, 0 /* configChanges */, false /* voiceInteraction */,
+                    false /* launchTaskBehind */, false /* alwaysFocusable */,
+                    0 /* targetSdkVersion */, 0 /* rotationAnimationHint */,
+                    0 /* inputDispatchingTimeoutNanos */, taskController.mService);
+            mToken = token;
+        }
+
+        @Override
+        AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token,
+                boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
+                boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
+                int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
+                boolean alwaysFocusable, AppWindowContainerController controller) {
+            return new TestAppWindowToken(service, token, voiceInteraction, dc,
+                    inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk,
+                    orientation,
+                    rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
+                    controller);
+        }
+
+        AppWindowToken getAppWindowToken(DisplayContent dc) {
+            return (AppWindowToken) dc.getWindowToken(mToken.asBinder());
+        }
+    }
+
+    public static class TestIApplicationToken implements IApplicationToken {
+
+        private final Binder mBinder = new Binder();
+        @Override
+        public IBinder asBinder() {
+            return mBinder;
+        }
+        @Override
+        public String getName() {
+            return null;
+        }
+    }
+
+    /** Used to track resize reports. */
+    public static class TestWindowState extends WindowState {
+        boolean resizeReported;
+
+        TestWindowState(WindowManagerService service, Session session, IWindow window,
+                WindowManager.LayoutParams attrs, WindowToken token) {
+            super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0,
+                    false /* ownerCanAddInternalSystemWindow */);
+        }
+
+        @Override
+        void reportResized() {
+            super.reportResized();
+            resizeReported = true;
+        }
+
+        @Override
+        public boolean isGoneForLayoutLw() {
+            return false;
+        }
+
+        @Override
+        void updateResizingWindowIfNeeded() {
+            // Used in AppWindowTokenTests#testLandscapeSeascapeRotationRelayout to deceive
+            // the system that it can actually update the window.
+            boolean hadSurface = mHasSurface;
+            mHasSurface = true;
+
+            super.updateResizingWindowIfNeeded();
+
+            mHasSurface = hadSurface;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
new file mode 100644
index 0000000..473a287
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2016 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.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.View.VISIBLE;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManagerGlobal;
+import android.testing.DexmakerShareClassLoaderRule;
+import android.util.Log;
+import android.view.Display;
+import android.view.DisplayInfo;
+import org.junit.Assert;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.view.IWindow;
+import android.view.WindowManager;
+
+import static android.app.AppOpsManager.OP_NONE;
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static org.mockito.Mockito.mock;
+
+import com.android.server.AttributeCache;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+
+/**
+ * Common base class for window manager unit test classes.
+ *
+ * Make sure any requests to WM hold the WM lock if needed b/73966377
+ */
+class WindowTestsBase {
+    private static final String TAG = WindowTestsBase.class.getSimpleName();
+    WindowManagerService sWm = null;  // TODO(roosa): rename to mWm in follow-up CL
+    private final IWindow mIWindow = new TestIWindow();
+    private Session mMockSession;
+    // The default display is removed in {@link #setUp} and then we iterate over all displays to
+    // make sure we don't collide with any existing display. If we run into no other display, the
+    // added display should be treated as default. This cannot be the default display
+    private static int sNextDisplayId = DEFAULT_DISPLAY + 1;
+    static int sNextStackId = 1000;
+
+    DisplayContent mDisplayContent;
+    DisplayInfo mDisplayInfo = new DisplayInfo();
+    WindowState mWallpaperWindow;
+    WindowState mImeWindow;
+    WindowState mImeDialogWindow;
+    WindowState mStatusBarWindow;
+    WindowState mDockedDividerWindow;
+    WindowState mNavBarWindow;
+    WindowState mAppWindow;
+    WindowState mChildAppWindowAbove;
+    WindowState mChildAppWindowBelow;
+    HashSet<WindowState> mCommonWindows;
+    WallpaperController mWallpaperController;
+
+    @Rule
+    public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+            new DexmakerShareClassLoaderRule();
+
+    @Rule
+    public final WindowManagerServiceRule mWmRule = new WindowManagerServiceRule();
+
+    static WindowState.PowerManagerWrapper mPowerManagerWrapper;  // TODO(roosa): make non-static.
+
+    @Before
+    public void setUp() throws Exception {
+        // If @Before throws an exception, the error isn't logged. This will make sure any failures
+        // in the set up are clear. This can be removed when b/37850063 is fixed.
+        try {
+            mMockSession = mock(Session.class);
+            mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class);
+
+            final Context context = InstrumentationRegistry.getTargetContext();
+            AttributeCache.init(context);
+
+            sWm = mWmRule.getWindowManagerService();
+            beforeCreateDisplay();
+
+            mWallpaperController = new WallpaperController(sWm);
+
+            context.getDisplay().getDisplayInfo(mDisplayInfo);
+            mDisplayContent = createNewDisplay();
+            sWm.mDisplayEnabled = true;
+            sWm.mDisplayReady = true;
+
+            // Set-up some common windows.
+            mCommonWindows = new HashSet();
+            synchronized (sWm.mWindowMap) {
+                mWallpaperWindow = createCommonWindow(null, TYPE_WALLPAPER, "wallpaperWindow");
+                mImeWindow = createCommonWindow(null, TYPE_INPUT_METHOD, "mImeWindow");
+                sWm.mInputMethodWindow = mImeWindow;
+                mImeDialogWindow = createCommonWindow(null, TYPE_INPUT_METHOD_DIALOG,
+                        "mImeDialogWindow");
+                mStatusBarWindow = createCommonWindow(null, TYPE_STATUS_BAR, "mStatusBarWindow");
+                mNavBarWindow = createCommonWindow(null, TYPE_NAVIGATION_BAR, "mNavBarWindow");
+                mDockedDividerWindow = createCommonWindow(null, TYPE_DOCK_DIVIDER,
+                        "mDockedDividerWindow");
+                mAppWindow = createCommonWindow(null, TYPE_BASE_APPLICATION, "mAppWindow");
+                mChildAppWindowAbove = createCommonWindow(mAppWindow,
+                        TYPE_APPLICATION_ATTACHED_DIALOG,
+                        "mChildAppWindowAbove");
+                mChildAppWindowBelow = createCommonWindow(mAppWindow,
+                        TYPE_APPLICATION_MEDIA_OVERLAY,
+                        "mChildAppWindowBelow");
+            }
+            // Adding a display will cause freezing the display. Make sure to wait until it's
+            // unfrozen to not run into race conditions with the tests.
+            waitUntilHandlersIdle();
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to set up test", e);
+            throw e;
+        }
+    }
+
+    void beforeCreateDisplay() {
+        // Called before display is created.
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // If @After throws an exception, the error isn't logged. This will make sure any failures
+        // in the tear down are clear. This can be removed when b/37850063 is fixed.
+        try {
+            final LinkedList<WindowState> nonCommonWindows = new LinkedList();
+
+            synchronized (sWm.mWindowMap) {
+                sWm.mRoot.forAllWindows(w -> {
+                    if (!mCommonWindows.contains(w)) {
+                        nonCommonWindows.addLast(w);
+                    }
+                }, true /* traverseTopToBottom */);
+
+                while (!nonCommonWindows.isEmpty()) {
+                    nonCommonWindows.pollLast().removeImmediately();
+                }
+
+                mDisplayContent.removeImmediately();
+                sWm.mInputMethodTarget = null;
+                sWm.mClosingApps.clear();
+                sWm.mOpeningApps.clear();
+            }
+
+            // Wait until everything is really cleaned up.
+            waitUntilHandlersIdle();
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to tear down test", e);
+            throw e;
+        }
+    }
+
+    /**
+     * @return A SurfaceBuilderFactory to inject in to the WindowManagerService during
+     *         set-up (or null).
+     */
+    SurfaceBuilderFactory getSurfaceBuilderFactory() {
+        return null;
+    }
+
+    private WindowState createCommonWindow(WindowState parent, int type, String name) {
+        synchronized (sWm.mWindowMap) {
+            final WindowState win = createWindow(parent, type, name);
+            mCommonWindows.add(win);
+            // Prevent common windows from been IMe targets
+            win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+            return win;
+        }
+    }
+
+    /** Asserts that the first entry is greater than the second entry. */
+    void assertGreaterThan(int first, int second) throws Exception {
+        Assert.assertTrue("Excepted " + first + " to be greater than " + second, first > second);
+    }
+
+    /** Asserts that the first entry is greater than the second entry. */
+    void assertLessThan(int first, int second) throws Exception {
+        Assert.assertTrue("Excepted " + first + " to be less than " + second, first < second);
+    }
+
+    /**
+     * Waits until the main handler for WM has processed all messages.
+     */
+    void waitUntilHandlersIdle() {
+        mWmRule.waitUntilWindowManagerHandlersIdle();
+    }
+
+    private WindowToken createWindowToken(
+            DisplayContent dc, int windowingMode, int activityType, int type) {
+        synchronized (sWm.mWindowMap) {
+            if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) {
+                return WindowTestUtils.createTestWindowToken(type, dc);
+            }
+
+            return createAppWindowToken(dc, windowingMode, activityType);
+        }
+    }
+
+    AppWindowToken createAppWindowToken(DisplayContent dc, int windowingMode, int activityType) {
+        final TaskStack stack = createStackControllerOnStackOnDisplay(windowingMode, activityType,
+                dc).mContainer;
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final WindowTestUtils.TestAppWindowToken appWindowToken =
+                WindowTestUtils.createTestAppWindowToken(dc);
+        task.addChild(appWindowToken, 0);
+        return appWindowToken;
+    }
+
+    WindowState createWindow(WindowState parent, int type, String name) {
+        synchronized (sWm.mWindowMap) {
+            return (parent == null)
+                    ? createWindow(parent, type, mDisplayContent, name)
+                    : createWindow(parent, type, parent.mToken, name);
+        }
+    }
+
+    WindowState createWindowOnStack(WindowState parent, int windowingMode, int activityType,
+            int type, DisplayContent dc, String name) {
+        synchronized (sWm.mWindowMap) {
+            final WindowToken token = createWindowToken(dc, windowingMode, activityType, type);
+            return createWindow(parent, type, token, name);
+        }
+    }
+
+    WindowState createAppWindow(Task task, int type, String name) {
+        synchronized (sWm.mWindowMap) {
+            final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+            task.addChild(token, 0);
+            return createWindow(null, type, token, name);
+        }
+    }
+
+    WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) {
+        synchronized (sWm.mWindowMap) {
+            final WindowToken token = createWindowToken(
+                    dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
+            return createWindow(parent, type, token, name);
+        }
+    }
+
+    WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name,
+            boolean ownerCanAddInternalSystemWindow) {
+        synchronized (sWm.mWindowMap) {
+            final WindowToken token = createWindowToken(
+                    dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
+            return createWindow(parent, type, token, name, 0 /* ownerId */,
+                    ownerCanAddInternalSystemWindow);
+        }
+    }
+
+    WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
+        synchronized (sWm.mWindowMap) {
+            return createWindow(parent, type, token, name, 0 /* ownerId */,
+                    false /* ownerCanAddInternalSystemWindow */);
+        }
+    }
+
+    WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
+            int ownerId, boolean ownerCanAddInternalSystemWindow) {
+        return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow,
+                sWm, mMockSession, mIWindow);
+    }
+
+    static WindowState createWindow(WindowState parent, int type, WindowToken token,
+            String name, int ownerId, boolean ownerCanAddInternalSystemWindow,
+            WindowManagerService service, Session session, IWindow iWindow) {
+        synchronized (service.mWindowMap) {
+            final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
+            attrs.setTitle(name);
+
+            final WindowState w = new WindowState(service, session, iWindow, token, parent,
+                    OP_NONE,
+                    0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow,
+                    mPowerManagerWrapper);
+            // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
+            // adding it to the token...
+            token.addWindow(w);
+            return w;
+        }
+    }
+
+    /** Creates a {@link TaskStack} and adds it to the specified {@link DisplayContent}. */
+    TaskStack createTaskStackOnDisplay(DisplayContent dc) {
+        synchronized (sWm.mWindowMap) {
+            return createStackControllerOnDisplay(dc).mContainer;
+        }
+    }
+
+    StackWindowController createStackControllerOnDisplay(DisplayContent dc) {
+        synchronized (sWm.mWindowMap) {
+            return createStackControllerOnStackOnDisplay(
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, dc);
+        }
+    }
+
+    StackWindowController createStackControllerOnStackOnDisplay(
+            int windowingMode, int activityType, DisplayContent dc) {
+        synchronized (sWm.mWindowMap) {
+            final Configuration overrideConfig = new Configuration();
+            overrideConfig.windowConfiguration.setWindowingMode(windowingMode);
+            overrideConfig.windowConfiguration.setActivityType(activityType);
+            final int stackId = ++sNextStackId;
+            final StackWindowController controller = new StackWindowController(stackId, null,
+                    dc.getDisplayId(), true /* onTop */, new Rect(), sWm);
+            controller.onOverrideConfigurationChanged(overrideConfig);
+            return controller;
+        }
+    }
+
+    /** Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
+    Task createTaskInStack(TaskStack stack, int userId) {
+        return WindowTestUtils.createTaskInStack(sWm, stack, userId);
+    }
+
+    /** Creates a {@link DisplayContent} and adds it to the system. */
+    DisplayContent createNewDisplay() {
+        final int displayId = sNextDisplayId++;
+        final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
+                mDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
+        synchronized (sWm.mWindowMap) {
+            return new DisplayContent(display, sWm, mWallpaperController,
+                    mock(DisplayWindowController.class));
+        }
+    }
+
+    /** Creates a {@link com.android.server.wm.WindowTestUtils.TestWindowState} */
+    WindowTestUtils.TestWindowState createWindowState(WindowManager.LayoutParams attrs,
+            WindowToken token) {
+        synchronized (sWm.mWindowMap) {
+            return new WindowTestUtils.TestWindowState(sWm, mMockSession, mIWindow, attrs, token);
+        }
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
new file mode 100644
index 0000000..e3b7174
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2016 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 org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for the {@link WindowToken} class.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.WindowTokenTests
+ */
+@SmallTest
+@FlakyTest(bugId = 74078662)
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowTokenTests extends WindowTestsBase {
+
+    @Test
+    public void testAddWindow() throws Exception {
+        final WindowTestUtils.TestWindowToken token =
+                WindowTestUtils.createTestWindowToken(0, mDisplayContent);
+
+        assertEquals(0, token.getWindowsCount());
+
+        final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1");
+        final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, token, "window11");
+        final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, token, "window12");
+        final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2");
+        final WindowState window3 = createWindow(null, TYPE_APPLICATION, token, "window3");
+
+        token.addWindow(window1);
+        // NOTE: Child windows will not be added to the token as window containers can only
+        // contain/reference their direct children.
+        token.addWindow(window11);
+        token.addWindow(window12);
+        token.addWindow(window2);
+        token.addWindow(window3);
+
+        // Should not contain the child windows that were added above.
+        assertEquals(3, token.getWindowsCount());
+        assertTrue(token.hasWindow(window1));
+        assertFalse(token.hasWindow(window11));
+        assertFalse(token.hasWindow(window12));
+        assertTrue(token.hasWindow(window2));
+        assertTrue(token.hasWindow(window3));
+    }
+
+    @Test
+    public void testChildRemoval() throws Exception {
+        final DisplayContent dc = mDisplayContent;
+        final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken(0, dc);
+
+        assertEquals(token, dc.getWindowToken(token.token));
+
+        final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1");
+        final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2");
+
+        window2.removeImmediately();
+        // The token should still be mapped in the display content since it still has a child.
+        assertEquals(token, dc.getWindowToken(token.token));
+
+        window1.removeImmediately();
+        // The token should have been removed from the display content since it no longer has a
+        // child.
+        assertEquals(null, dc.getWindowToken(token.token));
+    }
+
+    /**
+     * Test that a window token isn't orphaned by the system when it is requested to be removed.
+     * Tokens should only be removed from the system when all their windows are gone.
+     */
+    @Test
+    public void testTokenRemovalProcess() throws Exception {
+        final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken(
+                TYPE_TOAST, mDisplayContent, true /* persistOnEmpty */);
+
+        // Verify that the token is on the display
+        assertNotNull(mDisplayContent.getWindowToken(token.token));
+
+        final WindowState window1 = createWindow(null, TYPE_TOAST, token, "window1");
+        final WindowState window2 = createWindow(null, TYPE_TOAST, token, "window2");
+
+        mDisplayContent.removeWindowToken(token.token);
+        // Verify that the token is no longer mapped on the display
+        assertNull(mDisplayContent.getWindowToken(token.token));
+        // Verify that the token is still attached to its parent
+        assertNotNull(token.getParent());
+        // Verify that the token windows are still around.
+        assertEquals(2, token.getWindowsCount());
+
+        window1.removeImmediately();
+        // Verify that the token is still attached to its parent
+        assertNotNull(token.getParent());
+        // Verify that the other token window is still around.
+        assertEquals(1, token.getWindowsCount());
+
+        window2.removeImmediately();
+        // Verify that the token is no-longer attached to its parent
+        assertNull(token.getParent());
+        // Verify that the token windows are no longer attached to it.
+        assertEquals(0, token.getWindowsCount());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java
new file mode 100644
index 0000000..5085254
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2017 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 org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.filters.FlakyTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.wm.WindowManagerTraceProto;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Test class for {@link WindowTracing}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.WindowTracingTest
+ */
+@SmallTest
+@FlakyTest(bugId = 74078662)
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowTracingTest extends WindowTestsBase {
+
+    private static final byte[] MAGIC_HEADER = new byte[] {
+        0x9, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45,
+    };
+
+    private Context mTestContext;
+    private WindowTracing mWindowTracing;
+    private WindowManagerService mWmMock;
+    private File mFile;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mWmMock = mock(WindowManagerService.class);
+
+        mTestContext = InstrumentationRegistry.getContext();
+
+        mFile = mTestContext.getFileStreamPath("tracing_test.dat");
+        mFile.delete();
+
+        mWindowTracing = new WindowTracing(mFile);
+    }
+
+    @Test
+    public void isEnabled_returnsFalseByDefault() throws Exception {
+        assertFalse(mWindowTracing.isEnabled());
+    }
+
+    @Test
+    public void isEnabled_returnsTrueAfterStart() throws Exception {
+        mWindowTracing.startTrace(mock(PrintWriter.class));
+        assertTrue(mWindowTracing.isEnabled());
+    }
+
+    @Test
+    public void isEnabled_returnsFalseAfterStop() throws Exception {
+        mWindowTracing.startTrace(mock(PrintWriter.class));
+        mWindowTracing.stopTrace(mock(PrintWriter.class));
+        assertFalse(mWindowTracing.isEnabled());
+    }
+
+    @Test
+    public void trace_discared_whenNotTracing() throws Exception {
+        mWindowTracing.traceStateLocked("where", mWmMock);
+        verifyZeroInteractions(mWmMock);
+    }
+
+    @Test
+    public void trace_dumpsWindowManagerState_whenTracing() throws Exception {
+        mWindowTracing.startTrace(mock(PrintWriter.class));
+        mWindowTracing.traceStateLocked("where", mWmMock);
+
+        verify(mWmMock).writeToProtoLocked(any(), eq(true));
+    }
+
+    @Test
+    public void traceFile_startsWithMagicHeader() throws Exception {
+        mWindowTracing.startTrace(mock(PrintWriter.class));
+        mWindowTracing.stopTrace(mock(PrintWriter.class));
+
+        byte[] header = new byte[MAGIC_HEADER.length];
+        try (InputStream is = new FileInputStream(mFile)) {
+            assertEquals(MAGIC_HEADER.length, is.read(header));
+            assertArrayEquals(MAGIC_HEADER, header);
+        }
+    }
+
+    @Test
+    @Ignore("Figure out why this test is crashing when setting up mWmMock.")
+    public void tracing_endsUpInFile() throws Exception {
+        mWindowTracing.startTrace(mock(PrintWriter.class));
+
+        doAnswer((inv) -> {
+            inv.<ProtoOutputStream>getArgument(0).write(
+                    WindowManagerTraceProto.WHERE, "TEST_WM_PROTO");
+            return null;
+        }).when(mWmMock).writeToProtoLocked(any(), any());
+        mWindowTracing.traceStateLocked("TEST_WHERE", mWmMock);
+
+        mWindowTracing.stopTrace(mock(PrintWriter.class));
+
+        byte[] file = new byte[1000];
+        int fileLength;
+        try (InputStream is = new FileInputStream(mFile)) {
+            fileLength = is.read(file);
+            assertTrue(containsBytes(file, fileLength,
+                    "TEST_WHERE".getBytes(StandardCharsets.UTF_8)));
+            assertTrue(containsBytes(file, fileLength,
+                    "TEST_WM_PROTO".getBytes(StandardCharsets.UTF_8)));
+        }
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        mFile.delete();
+    }
+
+    /** Return true if {@code needle} appears anywhere in {@code haystack[0..length]} */
+    boolean containsBytes(byte[] haystack, int haystackLenght, byte[] needle) {
+        Preconditions.checkArgument(haystackLenght > 0);
+        Preconditions.checkArgument(needle.length > 0);
+
+        outer: for (int i = 0; i <= haystackLenght - needle.length; i++) {
+            for (int j = 0; j < needle.length; j++) {
+                if (haystack[i+j] != needle[j]) {
+                    continue outer;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Test
+    public void test_containsBytes() {
+        byte[] haystack = "hello_world".getBytes(StandardCharsets.UTF_8);
+        assertTrue(containsBytes(haystack, haystack.length,
+                "hello".getBytes(StandardCharsets.UTF_8)));
+        assertTrue(containsBytes(haystack, haystack.length,
+                "world".getBytes(StandardCharsets.UTF_8)));
+        assertFalse(containsBytes(haystack, 6,
+                "world".getBytes(StandardCharsets.UTF_8)));
+        assertFalse(containsBytes(haystack, haystack.length,
+                "world_".getBytes(StandardCharsets.UTF_8)));
+        assertFalse(containsBytes(haystack, haystack.length,
+                "absent".getBytes(StandardCharsets.UTF_8)));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
new file mode 100644
index 0000000..547be55
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2016 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.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+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.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.filters.FlakyTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+
+/**
+ * Tests for the {@link WindowLayersController} class.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.ZOrderingTests
+ */
+@SmallTest
+@FlakyTest(bugId = 74078662)
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ZOrderingTests extends WindowTestsBase {
+
+    private class LayerRecordingTransaction extends SurfaceControl.Transaction {
+        HashMap<SurfaceControl, Integer> mLayersForControl = new HashMap();
+        HashMap<SurfaceControl, SurfaceControl> mRelativeLayersForControl = new HashMap();
+
+        @Override
+        public SurfaceControl.Transaction setLayer(SurfaceControl sc, int layer) {
+            mRelativeLayersForControl.remove(sc);
+            mLayersForControl.put(sc, layer);
+            return super.setLayer(sc, layer);
+        }
+
+        @Override
+        public SurfaceControl.Transaction setRelativeLayer(SurfaceControl sc,
+                SurfaceControl relativeTo,
+                int layer) {
+            mRelativeLayersForControl.put(sc, relativeTo);
+            mLayersForControl.put(sc, layer);
+            return super.setRelativeLayer(sc, relativeTo, layer);
+        }
+
+        private int getLayer(SurfaceControl sc) {
+            return mLayersForControl.getOrDefault(sc, 0);
+        }
+
+        private SurfaceControl getRelativeLayer(SurfaceControl sc) {
+            return mRelativeLayersForControl.get(sc);
+        }
+    };
+
+    // We have WM use our Hierarchy recording subclass of SurfaceControl.Builder
+    // such that we can keep track of the parents of Surfaces as they are constructed.
+    private HashMap<SurfaceControl, SurfaceControl> mParentFor = new HashMap();
+
+    private class HierarchyRecorder extends SurfaceControl.Builder {
+        SurfaceControl mPendingParent;
+
+        HierarchyRecorder(SurfaceSession s) {
+            super(s);
+        }
+
+        public SurfaceControl.Builder setParent(SurfaceControl sc) {
+            mPendingParent = sc;
+            return super.setParent(sc);
+        }
+        public SurfaceControl build() {
+            SurfaceControl sc = super.build();
+            mParentFor.put(sc, mPendingParent);
+            mPendingParent = null;
+            return sc;
+        }
+    };
+
+    class HierarchyRecordingBuilderFactory implements SurfaceBuilderFactory {
+        public SurfaceControl.Builder make(SurfaceSession s) {
+            return new HierarchyRecorder(s);
+        }
+    };
+
+    private LayerRecordingTransaction mTransaction;
+
+    @Override
+    void beforeCreateDisplay() {
+        // We can't use @Before here because it may happen after WindowTestsBase @Before
+        // which is after construction of the DisplayContent, meaning the HierarchyRecorder
+        // would miss construction of the top-level layers.
+        mTransaction = new LayerRecordingTransaction();
+        sWm.mSurfaceBuilderFactory = new HierarchyRecordingBuilderFactory();
+        sWm.mTransactionFactory = () -> mTransaction;
+    }
+
+    @After
+    public void after() {
+        mTransaction.close();
+        mParentFor.clear();
+    }
+
+    LinkedList<SurfaceControl> getAncestors(LayerRecordingTransaction t, SurfaceControl sc) {
+        LinkedList<SurfaceControl> p = new LinkedList();
+        SurfaceControl current = sc;
+        do {
+            p.addLast(current);
+
+            SurfaceControl rs = t.getRelativeLayer(current);
+            if (rs != null) {
+                current = rs;
+            } else {
+                current = mParentFor.get(current);
+            }
+        } while (current != null);
+        return p;
+    }
+
+
+    void assertZOrderGreaterThan(LayerRecordingTransaction t, SurfaceControl left,
+            SurfaceControl right) throws Exception {
+        final LinkedList<SurfaceControl> leftParentChain = getAncestors(t, left);
+        final LinkedList<SurfaceControl> rightParentChain = getAncestors(t, right);
+
+        SurfaceControl commonAncestor = null;
+        SurfaceControl leftTop = leftParentChain.peekLast();
+        SurfaceControl rightTop = rightParentChain.peekLast();
+        while (leftTop != null && rightTop != null && leftTop == rightTop) {
+            commonAncestor = leftParentChain.removeLast();
+            rightParentChain.removeLast();
+            leftTop = leftParentChain.peekLast();
+            rightTop = rightParentChain.peekLast();
+        }
+
+        if (rightTop == null) { // right is the parent of left.
+            assertGreaterThan(t.getLayer(leftTop), 0);
+        } else if (leftTop == null) { // left is the parent of right.
+            assertGreaterThan(0, t.getLayer(rightTop));
+        } else {
+            assertGreaterThan(t.getLayer(leftTop),
+                    t.getLayer(rightTop));
+        }
+    }
+
+    void assertWindowHigher(WindowState left, WindowState right) throws Exception {
+        assertZOrderGreaterThan(mTransaction, left.getSurfaceControl(), right.getSurfaceControl());
+    }
+
+    WindowState createWindow(String name) {
+        return createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, name);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeWithNoTarget() throws Exception {
+        sWm.mInputMethodTarget = null;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // The Ime has an higher base layer than app windows and lower base layer than system
+        // windows, so it should be above app windows and below system windows if there isn't an IME
+        // target.
+        assertWindowHigher(mImeWindow, mChildAppWindowAbove);
+        assertWindowHigher(mImeWindow, mAppWindow);
+        assertWindowHigher(mNavBarWindow, mImeWindow);
+        assertWindowHigher(mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowHigher(mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeWithAppTarget() throws Exception {
+        final WindowState imeAppTarget = createWindow("imeAppTarget");
+        sWm.mInputMethodTarget = imeAppTarget;
+
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // Ime should be above all app windows and below system windows if it is targeting an app
+        // window.
+        assertWindowHigher(mImeWindow, imeAppTarget);
+        assertWindowHigher(mImeWindow, mChildAppWindowAbove);
+        assertWindowHigher(mImeWindow, mAppWindow);
+        assertWindowHigher(mNavBarWindow, mImeWindow);
+        assertWindowHigher(mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowHigher(mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeWithAppTargetWithChildWindows() throws Exception {
+        final WindowState imeAppTarget = createWindow("imeAppTarget");
+        final WindowState imeAppTargetChildAboveWindow = createWindow(imeAppTarget,
+                TYPE_APPLICATION_ATTACHED_DIALOG, imeAppTarget.mToken,
+                "imeAppTargetChildAboveWindow");
+        final WindowState imeAppTargetChildBelowWindow = createWindow(imeAppTarget,
+                TYPE_APPLICATION_MEDIA_OVERLAY, imeAppTarget.mToken,
+                "imeAppTargetChildBelowWindow");
+
+        sWm.mInputMethodTarget = imeAppTarget;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // Ime should be above all app windows except for child windows that are z-ordered above it
+        // and below system windows if it is targeting an app window.
+        assertWindowHigher(mImeWindow, imeAppTarget);
+        assertWindowHigher(imeAppTargetChildAboveWindow, mImeWindow);
+        assertWindowHigher(mImeWindow, mChildAppWindowAbove);
+        assertWindowHigher(mImeWindow, mAppWindow);
+        assertWindowHigher(mNavBarWindow, mImeWindow);
+        assertWindowHigher(mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowHigher(mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeWithAppTargetAndAppAbove() throws Exception {
+        final WindowState appBelowImeTarget = createWindow("appBelowImeTarget");
+        final WindowState imeAppTarget = createWindow("imeAppTarget");
+        final WindowState appAboveImeTarget = createWindow("appAboveImeTarget");
+
+        sWm.mInputMethodTarget = imeAppTarget;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // Ime should be above all app windows except for non-fullscreen app window above it and
+        // below system windows if it is targeting an app window.
+        assertWindowHigher(mImeWindow, imeAppTarget);
+        assertWindowHigher(mImeWindow, appBelowImeTarget);
+        assertWindowHigher(appAboveImeTarget, mImeWindow);
+        assertWindowHigher(mImeWindow, mChildAppWindowAbove);
+        assertWindowHigher(mImeWindow, mAppWindow);
+        assertWindowHigher(mNavBarWindow, mImeWindow);
+        assertWindowHigher(mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowHigher(mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeNonAppImeTarget() throws Exception {
+        final WindowState imeSystemOverlayTarget = createWindow(null, TYPE_SYSTEM_OVERLAY,
+                mDisplayContent, "imeSystemOverlayTarget",
+                true /* ownerCanAddInternalSystemWindow */);
+
+        sWm.mInputMethodTarget = imeSystemOverlayTarget;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // The IME target base layer is higher than all window except for the nav bar window, so the
+        // IME should be above all windows except for the nav bar.
+        assertWindowHigher(mImeWindow, imeSystemOverlayTarget);
+        assertWindowHigher(mImeWindow, mChildAppWindowAbove);
+        assertWindowHigher(mImeWindow, mAppWindow);
+        assertWindowHigher(mImeWindow, mDockedDividerWindow);
+
+        // The IME has a higher base layer than the status bar so we may expect it to go
+        // above the status bar once they are both in the Non-App layer, as past versions of this
+        // test enforced. However this seems like the wrong behavior unless the status bar is the
+        // IME target.
+        assertWindowHigher(mNavBarWindow, mImeWindow);
+        assertWindowHigher(mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowHigher(mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForStatusBarImeTarget() throws Exception {
+        sWm.mInputMethodTarget = mStatusBarWindow;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        assertWindowHigher(mImeWindow, mChildAppWindowAbove);
+        assertWindowHigher(mImeWindow, mAppWindow);
+        assertWindowHigher(mImeWindow, mDockedDividerWindow);
+        assertWindowHigher(mImeWindow, mStatusBarWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowHigher(mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testStackLayers() throws Exception {
+        final WindowState anyWindow1 = createWindow("anyWindow");
+        final WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED,
+                ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent,
+                "pinnedStackWindow");
+        final WindowState dockedStackWindow = createWindowOnStack(null,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
+                mDisplayContent, "dockedStackWindow");
+        final WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
+                mDisplayContent, "assistantStackWindow");
+        final WindowState homeActivityWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_HOME, TYPE_BASE_APPLICATION,
+                mDisplayContent, "homeActivityWindow");
+        final WindowState anyWindow2 = createWindow("anyWindow2");
+
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        assertWindowHigher(dockedStackWindow, homeActivityWindow);
+        assertWindowHigher(assistantStackWindow, homeActivityWindow);
+        assertWindowHigher(pinnedStackWindow, homeActivityWindow);
+        assertWindowHigher(anyWindow1, homeActivityWindow);
+        assertWindowHigher(anyWindow2, homeActivityWindow);
+        assertWindowHigher(pinnedStackWindow, anyWindow1);
+        assertWindowHigher(pinnedStackWindow, anyWindow2);
+        assertWindowHigher(pinnedStackWindow, dockedStackWindow);
+        assertWindowHigher(pinnedStackWindow, assistantStackWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForSysUiPanels() throws Exception {
+        final WindowState navBarPanel =
+                createWindow(null, TYPE_NAVIGATION_BAR_PANEL, mDisplayContent, "NavBarPanel");
+        final WindowState statusBarPanel =
+                createWindow(null, TYPE_STATUS_BAR_PANEL, mDisplayContent, "StatusBarPanel");
+        final WindowState statusBarSubPanel =
+                createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, mDisplayContent, "StatusBarSubPanel");
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // Ime should be above all app windows and below system windows if it is targeting an app
+        // window.
+        assertWindowHigher(navBarPanel, mNavBarWindow);
+        assertWindowHigher(statusBarPanel, mStatusBarWindow);
+        assertWindowHigher(statusBarSubPanel, statusBarPanel);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForNegativelyZOrderedSubtype() throws Exception {
+        // TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA
+        // then we can drop all negative layering on the windowing side.
+
+        final WindowState anyWindow = createWindow("anyWindow");
+        final WindowState child = createWindow(anyWindow, TYPE_APPLICATION_MEDIA, mDisplayContent,
+                "TypeApplicationMediaChild");
+        final WindowState mediaOverlayChild = createWindow(anyWindow, TYPE_APPLICATION_MEDIA_OVERLAY,
+                mDisplayContent, "TypeApplicationMediaOverlayChild");
+
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        assertWindowHigher(anyWindow, mediaOverlayChild);
+        assertWindowHigher(mediaOverlayChild, child);
+    }
+
+    @Test
+    public void testDockedDividerPosition() throws Exception {
+        final WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED,
+                ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent,
+                "pinnedStackWindow");
+        final WindowState splitScreenWindow = createWindowOnStack(null,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
+                mDisplayContent, "splitScreenWindow");
+        final WindowState splitScreenSecondaryWindow = createWindowOnStack(null,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
+                TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow");
+        final WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
+                mDisplayContent, "assistantStackWindow");
+
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        assertWindowHigher(mDockedDividerWindow, splitScreenWindow);
+        assertWindowHigher(mDockedDividerWindow, splitScreenSecondaryWindow);
+        assertWindowHigher(pinnedStackWindow, mDockedDividerWindow);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java
new file mode 100644
index 0000000..40a10e0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.wm.utils;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.*;
+
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.PointF;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+
+public class CoordinateTransformsTest {
+
+    private static final int W = 200;
+    private static final int H = 400;
+
+    private final Matrix mMatrix = new Matrix();
+
+    @Rule
+    public final ErrorCollector mErrorCollector = new ErrorCollector();
+
+    @Before
+    public void setUp() throws Exception {
+        mMatrix.setTranslate(0xdeadbeef, 0xdeadbeef);
+    }
+
+    @Test
+    public void transformPhysicalToLogicalCoordinates_rot0() throws Exception {
+        transformPhysicalToLogicalCoordinates(ROTATION_0, W, H, mMatrix);
+        assertThat(mMatrix, is(Matrix.IDENTITY_MATRIX));
+    }
+
+    @Test
+    public void transformPhysicalToLogicalCoordinates_rot90() throws Exception {
+        transformPhysicalToLogicalCoordinates(ROTATION_90, W, H, mMatrix);
+
+        checkDevicePoint(0, 0).mapsToLogicalPoint(0, W);
+        checkDevicePoint(W, H).mapsToLogicalPoint(H, 0);
+    }
+
+    @Test
+    public void transformPhysicalToLogicalCoordinates_rot180() throws Exception {
+        transformPhysicalToLogicalCoordinates(ROTATION_180, W, H, mMatrix);
+
+        checkDevicePoint(0, 0).mapsToLogicalPoint(W, H);
+        checkDevicePoint(W, H).mapsToLogicalPoint(0, 0);
+    }
+
+    @Test
+    public void transformPhysicalToLogicalCoordinates_rot270() throws Exception {
+        transformPhysicalToLogicalCoordinates(ROTATION_270, W, H, mMatrix);
+
+        checkDevicePoint(0, 0).mapsToLogicalPoint(H, 0);
+        checkDevicePoint(W, H).mapsToLogicalPoint(0, W);
+    }
+
+    private DevicePointAssertable checkDevicePoint(int x, int y) {
+        final Point devicePoint = new Point(x, y);
+        final float[] fs = new float[] {x, y};
+        mMatrix.mapPoints(fs);
+        final PointF transformedPoint = new PointF(fs[0], fs[1]);
+
+        return (expectedX, expectedY) -> {
+            mErrorCollector.checkThat("t(" + devicePoint + ")",
+                    transformedPoint, is(new PointF(expectedX, expectedY)));
+        };
+    }
+
+    public interface DevicePointAssertable {
+        void mapsToLogicalPoint(int x, int y);
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/InsetUtilsTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/InsetUtilsTest.java
new file mode 100644
index 0000000..d0f0fe3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/utils/InsetUtilsTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.wm.utils;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class InsetUtilsTest {
+
+    @Test
+    public void testAdd() throws Exception {
+        final Rect rect1 = new Rect(10, 20, 30, 40);
+        final Rect rect2 = new Rect(50, 60, 70, 80);
+        InsetUtils.addInsets(rect1, rect2);
+        assertEquals(new Rect(60, 80, 100, 120), rect1);
+    }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/RotationCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/RotationCacheTest.java
new file mode 100644
index 0000000..6bbc7eb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/utils/RotationCacheTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.wm.utils;
+
+import static android.util.Pair.create;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
+
+import com.android.server.wm.utils.RotationCache.RotationDependentComputation;
+
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@FlakyTest(bugId = 74078662)
+@Presubmit
+public class RotationCacheTest {
+
+    private RotationCache<Object, Pair<Object, Integer>> mCache;
+    private boolean mComputationCalled;
+
+    @Before
+    public void setUp() throws Exception {
+        mComputationCalled = false;
+        mCache = new RotationCache<>((o, rot) -> {
+            mComputationCalled = true;
+            return create(o, rot);
+        });
+    }
+
+    @Test
+    public void getOrCompute_computes() throws Exception {
+        assertThat(mCache.getOrCompute("hello", 0), equalTo(create("hello", 0)));
+        assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1)));
+        assertThat(mCache.getOrCompute("hello", 2), equalTo(create("hello", 2)));
+        assertThat(mCache.getOrCompute("hello", 3), equalTo(create("hello", 3)));
+    }
+
+    @Test
+    public void getOrCompute_sameParam_sameRot_hitsCache() throws Exception {
+        assertNotNull(mCache.getOrCompute("hello", 1));
+
+        mComputationCalled = false;
+        assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1)));
+        assertThat(mComputationCalled, is(false));
+    }
+
+    @Test
+    public void getOrCompute_sameParam_hitsCache_forAllRots() throws Exception {
+        assertNotNull(mCache.getOrCompute("hello", 3));
+        assertNotNull(mCache.getOrCompute("hello", 2));
+        assertNotNull(mCache.getOrCompute("hello", 1));
+        assertNotNull(mCache.getOrCompute("hello", 0));
+
+        mComputationCalled = false;
+        assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1)));
+        assertThat(mCache.getOrCompute("hello", 0), equalTo(create("hello", 0)));
+        assertThat(mCache.getOrCompute("hello", 2), equalTo(create("hello", 2)));
+        assertThat(mCache.getOrCompute("hello", 3), equalTo(create("hello", 3)));
+        assertThat(mComputationCalled, is(false));
+    }
+
+    @Test
+    public void getOrCompute_changingParam_recomputes() throws Exception {
+        assertNotNull(mCache.getOrCompute("hello", 1));
+
+        assertThat(mCache.getOrCompute("world", 1), equalTo(create("world", 1)));
+    }
+
+    @Test
+    public void getOrCompute_changingParam_clearsCacheForDifferentRots() throws Exception {
+        assertNotNull(mCache.getOrCompute("hello", 1));
+        assertNotNull(mCache.getOrCompute("world", 2));
+
+        mComputationCalled = false;
+        assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1)));
+        assertThat(mComputationCalled, is(true));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
new file mode 100644
index 0000000..f7addf6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.wm.utils;
+
+
+import static android.view.DisplayCutout.NO_CUTOUT;
+import static android.view.DisplayCutout.fromBoundingRect;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Size;
+import android.view.DisplayCutout;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link WmDisplayCutout}
+ *
+ * Run with: atest WmDisplayCutoutTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WmDisplayCutoutTest {
+
+    private final DisplayCutout mCutoutTop = new DisplayCutout(
+            new Rect(0, 100, 0, 0),
+            Arrays.asList(new Rect(50, 0, 75, 100)));
+
+    @Test
+    public void calculateRelativeTo_top() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(0, 0, 100, 20), 200, 400)
+                .calculateRelativeTo(new Rect(5, 5, 95, 195));
+
+        assertEquals(new Rect(0, 15, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void calculateRelativeTo_left() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(0, 0, 20, 100), 400, 200)
+                .calculateRelativeTo(new Rect(5, 5, 195, 95));
+
+        assertEquals(new Rect(15, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void calculateRelativeTo_bottom() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(0, 180, 100, 200), 100, 200)
+                .calculateRelativeTo(new Rect(5, 5, 95, 195));
+
+        assertEquals(new Rect(0, 0, 0, 15), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void calculateRelativeTo_right() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(180, 0, 200, 100), 200, 100)
+                .calculateRelativeTo(new Rect(5, 5, 195, 95));
+
+        assertEquals(new Rect(0, 0, 15, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void calculateRelativeTo_bounds() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(0, 0, 100, 20), 200, 400)
+                .calculateRelativeTo(new Rect(5, 10, 95, 180));
+
+        assertEquals(new Rect(-5, -10, 95, 10), cutout.getDisplayCutout().getBounds().getBounds());
+    }
+
+    @Test
+    public void computeSafeInsets_top() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(0, 0, 100, 20), 200, 400);
+
+        assertEquals(new Rect(0, 20, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_left() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(0, 0, 20, 100), 400, 200);
+
+        assertEquals(new Rect(20, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_bottom() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(0, 180, 100, 200), 100, 200);
+
+        assertEquals(new Rect(0, 0, 0, 20), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_right() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(180, 0, 200, 100), 200, 100);
+
+        assertEquals(new Rect(0, 0, 20, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_bounds() {
+        DisplayCutout cutout = WmDisplayCutout.computeSafeInsets(mCutoutTop, 1000,
+                2000).getDisplayCutout();
+
+        assertEquals(mCutoutTop.getBounds().getBounds(),
+                cutout.getBounds().getBounds());
+    }
+
+    @Test
+    public void test_equals() {
+        assertEquals(new WmDisplayCutout(NO_CUTOUT, null), new WmDisplayCutout(NO_CUTOUT, null));
+        assertEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)),
+                new WmDisplayCutout(mCutoutTop, new Size(1, 2)));
+
+        assertNotEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)),
+                new WmDisplayCutout(mCutoutTop, new Size(5, 6)));
+        assertNotEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)),
+                new WmDisplayCutout(NO_CUTOUT, new Size(1, 2)));
+    }
+
+    @Test
+    public void test_hashCode() {
+        assertEquals(new WmDisplayCutout(NO_CUTOUT, null).hashCode(),
+                new WmDisplayCutout(NO_CUTOUT, null).hashCode());
+        assertEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)).hashCode(),
+                new WmDisplayCutout(mCutoutTop, new Size(1, 2)).hashCode());
+    }
+}
\ No newline at end of file