| /* |
| * 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.ActivityTaskManager.INVALID_TASK_ID; |
| 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.WINDOWING_MODE_MULTI_WINDOW; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; |
| import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; |
| import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; |
| import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; |
| 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 android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED; |
| import static android.view.Surface.ROTATION_0; |
| import static android.view.Surface.ROTATION_90; |
| import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; |
| |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; |
| import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE; |
| import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; |
| import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.hamcrest.Matchers.not; |
| import static org.hamcrest.Matchers.sameInstance; |
| 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.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyBoolean; |
| import static org.mockito.ArgumentMatchers.anyString; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.ArgumentMatchers.same; |
| import static org.mockito.Mockito.atLeast; |
| import static org.mockito.Mockito.clearInvocations; |
| import static org.mockito.Mockito.never; |
| |
| import android.app.ActivityManager; |
| import android.app.ActivityOptions; |
| import android.app.TaskInfo; |
| import android.app.WindowConfiguration; |
| import android.content.ComponentName; |
| import android.content.Intent; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.ApplicationInfo; |
| import android.content.res.Configuration; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.os.IBinder; |
| import android.platform.test.annotations.Presubmit; |
| import android.util.DisplayMetrics; |
| import android.util.TypedXmlPullParser; |
| import android.util.TypedXmlSerializer; |
| import android.util.Xml; |
| import android.view.Display; |
| import android.view.DisplayInfo; |
| |
| import androidx.test.filters.MediumTest; |
| |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mockito; |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| |
| /** |
| * Test class for {@link Task}. |
| * |
| * Build/Install/Run: |
| * atest WmTests:TaskTests |
| */ |
| @MediumTest |
| @Presubmit |
| @RunWith(WindowTestRunner.class) |
| public class TaskTests extends WindowTestsBase { |
| |
| private static final String TASK_TAG = "task"; |
| |
| private Rect mParentBounds; |
| |
| @Before |
| public void setUp() throws Exception { |
| mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/); |
| removeGlobalMinSizeRestriction(); |
| } |
| |
| @Test |
| public void testRemoveContainer() { |
| final Task rootTask = createTask(mDisplayContent); |
| final Task task = createTaskInRootTask(rootTask, 0 /* userId */); |
| final ActivityRecord activity = createActivityRecord(mDisplayContent, task); |
| |
| task.remove(false /* withTransition */, "testRemoveContainer"); |
| // There is still an activity to be destroyed, so the task is not removed immediately. |
| assertNotNull(task.getParent()); |
| assertTrue(rootTask.hasChild()); |
| assertTrue(task.hasChild()); |
| assertTrue(activity.finishing); |
| |
| activity.destroyed("testRemoveContainer"); |
| // Assert that the container was removed after the activity is destroyed. |
| assertNull(task.getParent()); |
| assertEquals(0, task.getChildCount()); |
| assertNull(activity.getParent()); |
| verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(task); |
| verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask); |
| } |
| |
| @Test |
| public void testRemoveContainer_multipleNestedTasks() { |
| final Task rootTask = createTask(mDisplayContent); |
| rootTask.mCreatedByOrganizer = true; |
| final Task task1 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); |
| final Task task2 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); |
| final ActivityRecord activity1 = createActivityRecord(task1); |
| final ActivityRecord activity2 = createActivityRecord(task2); |
| activity1.setVisible(false); |
| |
| // All activities under the root task should be finishing. |
| rootTask.remove(true /* withTransition */, "test"); |
| assertTrue(activity1.finishing); |
| assertTrue(activity2.finishing); |
| |
| // After all activities activities are destroyed, the root task should also be removed. |
| activity1.removeImmediately(); |
| activity2.removeImmediately(); |
| assertFalse(rootTask.isAttached()); |
| } |
| |
| @Test |
| public void testRemoveContainer_deferRemoval() { |
| final Task rootTask = createTask(mDisplayContent); |
| final Task task = createTaskInRootTask(rootTask, 0 /* userId */); |
| final ActivityRecord activity = createActivityRecord(mDisplayContent, task); |
| |
| doReturn(true).when(task).shouldDeferRemoval(); |
| |
| task.removeIfPossible(); |
| // For the case of deferred removal the task will still be connected to the its app token |
| // until the task window container is removed. |
| assertNotNull(task.getParent()); |
| assertNotEquals(0, task.getChildCount()); |
| assertNotNull(activity.getParent()); |
| |
| task.removeImmediately(); |
| assertNull(task.getParent()); |
| assertEquals(0, task.getChildCount()); |
| assertNull(activity.getParent()); |
| } |
| |
| @Test |
| public void testReparent() { |
| final Task taskController1 = createTask(mDisplayContent); |
| final Task task = createTaskInRootTask(taskController1, 0 /* userId */); |
| final Task taskController2 = createTask(mDisplayContent); |
| final Task task2 = createTaskInRootTask(taskController2, 0 /* userId */); |
| |
| boolean gotException = false; |
| try { |
| task.reparent(taskController1, 0, false/* moveParents */, "testReparent"); |
| } catch (IllegalArgumentException e) { |
| gotException = true; |
| } |
| assertTrue("Should not be able to reparent to the same parent", gotException); |
| |
| gotException = false; |
| try { |
| task.reparent(null, 0, false/* moveParents */, "testReparent"); |
| } catch (Exception e) { |
| gotException = true; |
| } |
| assertTrue("Should not be able to reparent to a task that doesn't exist", gotException); |
| |
| task.reparent(taskController2, 0, false/* moveParents */, "testReparent"); |
| assertEquals(taskController2, task.getParent()); |
| assertEquals(0, task.getParent().mChildren.indexOf(task)); |
| assertEquals(1, task2.getParent().mChildren.indexOf(task2)); |
| } |
| |
| @Test |
| public void testReparent_BetweenDisplays() { |
| // Create first task on primary display. |
| final Task rootTask1 = createTask(mDisplayContent); |
| final Task task = createTaskInRootTask(rootTask1, 0 /* userId */); |
| assertEquals(mDisplayContent, rootTask1.getDisplayContent()); |
| |
| // Create second display and put second task on it. |
| final DisplayContent dc = createNewDisplay(); |
| final Task rootTask2 = createTask(dc); |
| final Task task2 = createTaskInRootTask(rootTask2, 0 /* userId */); |
| // Reparent and check state |
| clearInvocations(task); // reset the number of onDisplayChanged for task. |
| task.reparent(rootTask2, 0, false /* moveParents */, "testReparent_BetweenDisplays"); |
| assertEquals(rootTask2, task.getParent()); |
| assertEquals(0, task.getParent().mChildren.indexOf(task)); |
| assertEquals(1, task2.getParent().mChildren.indexOf(task2)); |
| verify(task, times(1)).onDisplayChanged(any()); |
| } |
| |
| @Test |
| public void testBounds() { |
| final Task rootTask1 = createTask(mDisplayContent); |
| final Task task = createTaskInRootTask(rootTask1, 0 /* userId */); |
| |
| // Check that setting bounds also updates surface position |
| task.setWindowingMode(WINDOWING_MODE_FREEFORM); |
| Rect bounds = new Rect(10, 10, 100, 200); |
| task.setBounds(bounds); |
| assertEquals(new Point(bounds.left, bounds.top), task.getLastSurfacePosition()); |
| } |
| |
| @Test |
| public void testIsInTask() { |
| final Task task1 = createTask(mDisplayContent); |
| final Task task2 = createTask(mDisplayContent); |
| final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task1); |
| final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task2); |
| assertEquals(activity1, task1.isInTask(activity1)); |
| assertNull(task1.isInTask(activity2)); |
| } |
| |
| @Test |
| public void testPerformClearTop() { |
| final Task task = createTask(mDisplayContent); |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); |
| // Detach from process so the activities can be removed from hierarchy when finishing. |
| activity1.detachFromProcess(); |
| activity2.detachFromProcess(); |
| assertNull(task.performClearTop(activity1, 0 /* launchFlags */)); |
| assertFalse(task.hasChild()); |
| // In real case, the task should be preserved for adding new activity. |
| assertTrue(task.isAttached()); |
| } |
| |
| @Test |
| public void testRemoveChildForOverlayTask() { |
| final Task task = createTask(mDisplayContent); |
| final int taskId = task.mTaskId; |
| final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task); |
| final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task); |
| final ActivityRecord activity3 = createActivityRecord(mDisplayContent, task); |
| activity1.setTaskOverlay(true); |
| activity2.setTaskOverlay(true); |
| activity3.setTaskOverlay(true); |
| |
| assertEquals(3, task.getChildCount()); |
| assertTrue(task.onlyHasTaskOverlayActivities(true)); |
| |
| task.removeChild(activity1); |
| |
| verify(task.mTaskSupervisor).removeTask(any(), anyBoolean(), anyBoolean(), anyString()); |
| assertEquals(2, task.getChildCount()); |
| task.forAllActivities((r) -> { |
| assertTrue(r.finishing); |
| }); |
| } |
| |
| @Test |
| public void testSwitchUser() { |
| final Task rootTask = createTask(mDisplayContent); |
| final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */); |
| final Task leafTask1 = createTaskInRootTask(childTask, 10 /* userId */); |
| final Task leafTask2 = createTaskInRootTask(childTask, 0 /* userId */); |
| assertEquals(1, rootTask.getChildCount()); |
| assertEquals(leafTask2, childTask.getTopChild()); |
| |
| doReturn(true).when(leafTask1).showToCurrentUser(); |
| rootTask.switchUser(10); |
| assertEquals(1, rootTask.getChildCount()); |
| assertEquals(leafTask1, childTask.getTopChild()); |
| } |
| |
| @Test |
| public void testEnsureActivitiesVisible() { |
| final Task rootTask = createTask(mDisplayContent); |
| final Task leafTask1 = createTaskInRootTask(rootTask, 0 /* userId */); |
| final Task leafTask2 = createTaskInRootTask(rootTask, 0 /* userId */); |
| final ActivityRecord activity1 = createActivityRecord(mDisplayContent, leafTask1); |
| final ActivityRecord activity2 = createActivityRecord(mDisplayContent, leafTask2); |
| |
| // Check visibility of occluded tasks |
| doReturn(false).when(leafTask1).shouldBeVisible(any()); |
| doReturn(true).when(leafTask2).shouldBeVisible(any()); |
| rootTask.ensureActivitiesVisible( |
| null /* starting */ , 0 /* configChanges */, false /* preserveWindows */); |
| assertFalse(activity1.isVisible()); |
| assertTrue(activity2.isVisible()); |
| |
| // Check visibility of not occluded tasks |
| doReturn(true).when(leafTask1).shouldBeVisible(any()); |
| doReturn(true).when(leafTask2).shouldBeVisible(any()); |
| rootTask.ensureActivitiesVisible( |
| null /* starting */ , 0 /* configChanges */, false /* preserveWindows */); |
| assertTrue(activity1.isVisible()); |
| assertTrue(activity2.isVisible()); |
| } |
| |
| @Test |
| public void testResolveNonResizableTaskWindowingMode() { |
| // Test with no support non-resizable in multi window regardless the screen size. |
| mAtm.mSupportsNonResizableMultiWindow = -1; |
| |
| final Task task = createTask(mDisplayContent); |
| Configuration parentConfig = task.getParent().getConfiguration(); |
| parentConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); |
| doReturn(false).when(task).isResizeable(); |
| WindowConfiguration requestedOverride = |
| task.getRequestedOverrideConfiguration().windowConfiguration; |
| WindowConfiguration resolvedOverride = |
| task.getResolvedOverrideConfiguration().windowConfiguration; |
| |
| // The resolved override windowing mode of a non-resizeable task should be resolved as |
| // fullscreen even as a child of a freeform display. |
| requestedOverride.setWindowingMode(WINDOWING_MODE_UNDEFINED); |
| task.resolveOverrideConfiguration(parentConfig); |
| assertThat(resolvedOverride.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN); |
| |
| // The resolved override windowing mode of a non-resizeable task should be resolved as |
| // fullscreen, even when requested as freeform windowing mode |
| requestedOverride.setWindowingMode(WINDOWING_MODE_FREEFORM); |
| task.resolveOverrideConfiguration(parentConfig); |
| assertThat(resolvedOverride.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN); |
| |
| // The resolved override windowing mode of a non-resizeable task can be undefined as long |
| // as its parents is not in multi-window mode. |
| parentConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); |
| requestedOverride.setWindowingMode(WINDOWING_MODE_UNDEFINED); |
| task.resolveOverrideConfiguration(parentConfig); |
| assertThat(resolvedOverride.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED); |
| } |
| |
| @Test |
| public void testHandlesOrientationChangeFromDescendant() { |
| final Task rootTask = createTask(mDisplayContent, |
| WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); |
| final Task leafTask1 = createTaskInRootTask(rootTask, 0 /* userId */); |
| final Task leafTask2 = createTaskInRootTask(rootTask, 0 /* userId */); |
| leafTask1.getWindowConfiguration().setActivityType(ACTIVITY_TYPE_HOME); |
| leafTask2.getWindowConfiguration().setActivityType(ACTIVITY_TYPE_STANDARD); |
| |
| assertEquals(leafTask2, rootTask.getTopChild()); |
| assertTrue(rootTask.handlesOrientationChangeFromDescendant()); |
| // Treat orientation request from home as handled. |
| assertTrue(leafTask1.handlesOrientationChangeFromDescendant()); |
| // Orientation request from standard activity in multi window will not be handled. |
| assertFalse(leafTask2.handlesOrientationChangeFromDescendant()); |
| } |
| |
| @Test |
| public void testAlwaysOnTop() { |
| final Task task = createTask(mDisplayContent); |
| task.setAlwaysOnTop(true); |
| task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); |
| assertTrue(task.isAlwaysOnTop()); |
| |
| task.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */); |
| assertFalse(task.isAlwaysOnTop()); |
| } |
| |
| @Test |
| public void testRestoreWindowedTask() throws Exception { |
| final Task expected = createTask(64); |
| expected.mLastNonFullscreenBounds = new Rect(50, 50, 100, 100); |
| |
| final byte[] serializedBytes = serializeToBytes(expected); |
| final Task actual = restoreFromBytes(serializedBytes); |
| assertEquals(expected.mTaskId, actual.mTaskId); |
| assertEquals(expected.mLastNonFullscreenBounds, actual.mLastNonFullscreenBounds); |
| } |
| |
| /** Ensure we have no chance to modify the original intent. */ |
| @Test |
| public void testCopyBaseIntentForTaskInfo() { |
| final Task task = createTask(1); |
| task.setTaskDescription(new ActivityManager.TaskDescription()); |
| final TaskInfo info = task.getTaskInfo(); |
| |
| // The intent of info should be a copy so assert that they are different instances. |
| Assert.assertThat(info.baseIntent, not(sameInstance(task.getBaseIntent()))); |
| } |
| |
| @Test |
| public void testReturnsToHomeRootTask() throws Exception { |
| final Task task = createTask(1); |
| spyOn(task); |
| doReturn(true).when(task).hasChild(); |
| assertFalse(task.returnsToHomeRootTask()); |
| task.intent = null; |
| assertFalse(task.returnsToHomeRootTask()); |
| task.intent = new Intent(); |
| assertFalse(task.returnsToHomeRootTask()); |
| task.intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME); |
| assertTrue(task.returnsToHomeRootTask()); |
| } |
| |
| /** Ensures that empty bounds cause appBounds to inherit from parent. */ |
| @Test |
| public void testAppBounds_EmptyBounds() { |
| final Rect emptyBounds = new Rect(); |
| testRootTaskBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds, |
| mParentBounds); |
| } |
| |
| /** Ensures that bounds on freeform root tasks are not clipped. */ |
| @Test |
| public void testAppBounds_FreeFormBounds() { |
| final Rect freeFormBounds = new Rect(mParentBounds); |
| freeFormBounds.offset(10, 10); |
| testRootTaskBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds, |
| freeFormBounds); |
| } |
| |
| /** Ensures that fully contained bounds are not clipped. */ |
| @Test |
| public void testAppBounds_ContainedBounds() { |
| final Rect insetBounds = new Rect(mParentBounds); |
| insetBounds.inset(5, 5, 5, 5); |
| testRootTaskBoundsConfiguration( |
| WINDOWING_MODE_FREEFORM, mParentBounds, insetBounds, insetBounds); |
| } |
| |
| @Test |
| public void testFitWithinBounds() { |
| final Rect parentBounds = new Rect(10, 10, 200, 200); |
| TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea(); |
| Task rootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FREEFORM, |
| ACTIVITY_TYPE_STANDARD, true /* onTop */); |
| Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); |
| final Configuration parentConfig = rootTask.getConfiguration(); |
| parentConfig.windowConfiguration.setBounds(parentBounds); |
| parentConfig.densityDpi = DisplayMetrics.DENSITY_DEFAULT; |
| |
| // check top and left |
| Rect reqBounds = new Rect(-190, -190, 0, 0); |
| task.setBounds(reqBounds); |
| // Make sure part of it is exposed |
| assertTrue(task.getBounds().right > parentBounds.left); |
| assertTrue(task.getBounds().bottom > parentBounds.top); |
| // Should still be more-or-less in that corner |
| assertTrue(task.getBounds().left <= parentBounds.left); |
| assertTrue(task.getBounds().top <= parentBounds.top); |
| |
| assertEquals(reqBounds.width(), task.getBounds().width()); |
| assertEquals(reqBounds.height(), task.getBounds().height()); |
| |
| // check bottom and right |
| reqBounds = new Rect(210, 210, 400, 400); |
| task.setBounds(reqBounds); |
| // Make sure part of it is exposed |
| assertTrue(task.getBounds().left < parentBounds.right); |
| assertTrue(task.getBounds().top < parentBounds.bottom); |
| // Should still be more-or-less in that corner |
| assertTrue(task.getBounds().right >= parentBounds.right); |
| assertTrue(task.getBounds().bottom >= parentBounds.bottom); |
| |
| assertEquals(reqBounds.width(), task.getBounds().width()); |
| assertEquals(reqBounds.height(), task.getBounds().height()); |
| } |
| |
| /** |
| * Tests that a task with forced orientation has orientation-consistent bounds within the |
| * parent. |
| */ |
| @Test |
| public void testFullscreenBoundsForcedOrientation() { |
| final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); |
| final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920); |
| final DisplayContent display = new TestDisplayContent.Builder(mAtm, |
| fullScreenBounds.width(), fullScreenBounds.height()).setCanRotate(false).build(); |
| assertNotNull(mRootWindowContainer.getDisplayContent(display.mDisplayId)); |
| // Fix the display orientation to landscape which is the natural rotation (0) for the test |
| // display. |
| final DisplayRotation dr = display.mDisplayContent.getDisplayRotation(); |
| dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED); |
| dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0); |
| |
| final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) |
| .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); |
| final Task task = rootTask.getBottomMostTask(); |
| final ActivityRecord root = task.getTopNonFinishingActivity(); |
| |
| assertEquals(fullScreenBounds, task.getBounds()); |
| |
| // Setting app to fixed portrait fits within parent |
| root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); |
| assertEquals(root, task.getRootActivity()); |
| assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); |
| // Portrait orientation is enforced on activity level. Task should fill fullscreen bounds. |
| assertThat(task.getBounds().height()).isLessThan(task.getBounds().width()); |
| assertEquals(fullScreenBounds, task.getBounds()); |
| |
| // Top activity gets used |
| final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).setParentTask(rootTask) |
| .build(); |
| assertEquals(top, task.getTopNonFinishingActivity()); |
| top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); |
| assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height()); |
| assertEquals(task.getBounds().width(), fullScreenBounds.width()); |
| |
| // Setting app to unspecified restores |
| top.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); |
| assertEquals(fullScreenBounds, task.getBounds()); |
| |
| // Setting app to fixed landscape and changing display |
| top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); |
| // Fix the display orientation to portrait which is 90 degrees for the test display. |
| dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90); |
| |
| // Fixed orientation request should be resolved on activity level. Task fills display |
| // bounds. |
| assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width()); |
| assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height()); |
| assertEquals(fullScreenBoundsPort, task.getBounds()); |
| |
| // in FREEFORM, no constraint |
| final Rect freeformBounds = new Rect(display.getBounds()); |
| freeformBounds.inset((int) (freeformBounds.width() * 0.2), |
| (int) (freeformBounds.height() * 0.2)); |
| rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM); |
| task.setBounds(freeformBounds); |
| assertEquals(freeformBounds, task.getBounds()); |
| |
| // FULLSCREEN letterboxes bounds on activity level, no constraint on task level. |
| rootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); |
| rootTask.setBounds(null); |
| assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width()); |
| assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height()); |
| assertEquals(fullScreenBoundsPort, task.getBounds()); |
| |
| // FREEFORM restores bounds as before |
| rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM); |
| assertEquals(freeformBounds, task.getBounds()); |
| } |
| |
| @Test |
| public void testReportsOrientationRequestInLetterboxForOrientation() { |
| final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); |
| final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920); |
| final DisplayContent display = new TestDisplayContent.Builder(mAtm, |
| fullScreenBounds.width(), fullScreenBounds.height()).setCanRotate(false).build(); |
| assertNotNull(mRootWindowContainer.getDisplayContent(display.mDisplayId)); |
| // Fix the display orientation to landscape which is the natural rotation (0) for the test |
| // display. |
| final DisplayRotation dr = display.mDisplayContent.getDisplayRotation(); |
| dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED); |
| dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0); |
| |
| final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) |
| .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); |
| final Task task = rootTask.getBottomMostTask(); |
| ActivityRecord root = task.getTopNonFinishingActivity(); |
| |
| assertEquals(fullScreenBounds, task.getBounds()); |
| |
| // Setting app to fixed portrait fits within parent on activity level. Task fills parent. |
| root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); |
| assertThat(root.getBounds().width()).isLessThan(root.getBounds().height()); |
| assertEquals(task.getBounds(), fullScreenBounds); |
| |
| assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getOrientation()); |
| } |
| |
| @Test |
| public void testIgnoresForcedOrientationWhenParentHandles() { |
| final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); |
| DisplayContent display = new TestDisplayContent.Builder( |
| mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build(); |
| |
| display.getRequestedOverrideConfiguration().orientation = |
| Configuration.ORIENTATION_LANDSCAPE; |
| display.onRequestedOverrideConfigurationChanged( |
| display.getRequestedOverrideConfiguration()); |
| Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) |
| .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); |
| Task task = rootTask.getBottomMostTask(); |
| ActivityRecord root = task.getTopNonFinishingActivity(); |
| |
| final WindowContainer parentWindowContainer = |
| new WindowContainer(mSystemServicesTestRule.getWindowManagerService()); |
| spyOn(parentWindowContainer); |
| parentWindowContainer.setBounds(fullScreenBounds); |
| doReturn(parentWindowContainer).when(task).getParent(); |
| doReturn(display.getDefaultTaskDisplayArea()).when(task).getDisplayArea(); |
| doReturn(rootTask).when(task).getRootTask(); |
| doReturn(true).when(parentWindowContainer).handlesOrientationChangeFromDescendant(); |
| |
| // Setting app to fixed portrait fits within parent, but Task shouldn't adjust the |
| // bounds because its parent says it will handle it at a later time. |
| root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); |
| assertEquals(root, task.getRootActivity()); |
| assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); |
| assertEquals(fullScreenBounds, task.getBounds()); |
| } |
| |
| @Test |
| public void testComputeConfigResourceOverrides() { |
| final Rect fullScreenBounds = new Rect(0, 0, 1080, 1920); |
| TestDisplayContent display = new TestDisplayContent.Builder( |
| mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build(); |
| final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build(); |
| final Configuration inOutConfig = new Configuration(); |
| final Configuration parentConfig = new Configuration(); |
| final Rect parentBounds = new Rect(0, 0, 250, 500); |
| final Rect parentAppBounds = new Rect(0, 0, 250, 480); |
| parentConfig.windowConfiguration.setBounds(parentBounds); |
| parentConfig.windowConfiguration.setAppBounds(parentAppBounds); |
| parentConfig.densityDpi = 400; |
| parentConfig.screenHeightDp = (parentBounds.bottom * 160) / parentConfig.densityDpi; // 200 |
| parentConfig.screenWidthDp = (parentBounds.right * 160) / parentConfig.densityDpi; // 100 |
| parentConfig.windowConfiguration.setRotation(ROTATION_0); |
| |
| // By default, the input bounds will fill parent. |
| task.computeConfigResourceOverrides(inOutConfig, parentConfig); |
| |
| assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp); |
| assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp); |
| assertEquals(parentAppBounds, inOutConfig.windowConfiguration.getAppBounds()); |
| assertEquals(Configuration.ORIENTATION_PORTRAIT, inOutConfig.orientation); |
| |
| // If bounds are overridden, config properties should be made to match. Surface hierarchy |
| // will crop for policy. |
| inOutConfig.setToDefaults(); |
| final int longSide = 960; |
| final int shortSide = 540; |
| parentConfig.densityDpi = 192; |
| final Rect largerPortraitBounds = new Rect(0, 0, shortSide, longSide); |
| inOutConfig.windowConfiguration.setBounds(largerPortraitBounds); |
| task.computeConfigResourceOverrides(inOutConfig, parentConfig); |
| // The override bounds are beyond the parent, the out appBounds should not be intersected |
| // by parent appBounds. |
| assertEquals(largerPortraitBounds, inOutConfig.windowConfiguration.getAppBounds()); |
| assertEquals(800, inOutConfig.screenHeightDp); // 960/(192/160) = 800 |
| assertEquals(450, inOutConfig.screenWidthDp); // 540/(192/160) = 450 |
| |
| inOutConfig.setToDefaults(); |
| // Landscape bounds. |
| final Rect largerLandscapeBounds = new Rect(0, 0, longSide, shortSide); |
| inOutConfig.windowConfiguration.setBounds(largerLandscapeBounds); |
| |
| // Setup the display with a top stable inset. The later assertion will ensure the inset is |
| // excluded from screenHeightDp. |
| final int statusBarHeight = 100; |
| final DisplayPolicy policy = display.getDisplayPolicy(); |
| doAnswer(invocationOnMock -> { |
| final Rect insets = invocationOnMock.<Rect>getArgument(0); |
| insets.top = statusBarHeight; |
| return null; |
| }).when(policy).convertNonDecorInsetsToStableInsets(any(), eq(ROTATION_0)); |
| |
| // Without limiting to be inside the parent bounds, the out screen size should keep relative |
| // to the input bounds. |
| final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); |
| final ActivityRecord.CompatDisplayInsets compatInsets = |
| new ActivityRecord.CompatDisplayInsets( |
| display, activity, /* fixedOrientationBounds= */ null); |
| task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatInsets); |
| |
| assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds()); |
| final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; |
| final int expectedHeightDp = (int) ((shortSide - statusBarHeight) / density + 0.5f); |
| assertEquals(expectedHeightDp, inOutConfig.screenHeightDp); |
| final int expectedWidthDp = (int) (longSide / density + 0.5f); |
| assertEquals(expectedWidthDp, inOutConfig.screenWidthDp); |
| assertEquals(Configuration.ORIENTATION_LANDSCAPE, inOutConfig.orientation); |
| } |
| |
| @Test |
| public void testComputeConfigResourceLayoutOverrides() { |
| final Rect fullScreenBounds = new Rect(0, 0, 1000, 2500); |
| TestDisplayContent display = new TestDisplayContent.Builder( |
| mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build(); |
| final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build(); |
| final Configuration inOutConfig = new Configuration(); |
| final Configuration parentConfig = new Configuration(); |
| final Rect nonLongBounds = new Rect(0, 0, 1000, 1250); |
| parentConfig.windowConfiguration.setBounds(fullScreenBounds); |
| parentConfig.windowConfiguration.setAppBounds(fullScreenBounds); |
| parentConfig.densityDpi = 400; |
| parentConfig.screenHeightDp = (fullScreenBounds.bottom * 160) / parentConfig.densityDpi; |
| parentConfig.screenWidthDp = (fullScreenBounds.right * 160) / parentConfig.densityDpi; |
| parentConfig.windowConfiguration.setRotation(ROTATION_0); |
| |
| // Set BOTH screenW/H to an override value |
| inOutConfig.screenWidthDp = nonLongBounds.width() * 160 / parentConfig.densityDpi; |
| inOutConfig.screenHeightDp = nonLongBounds.height() * 160 / parentConfig.densityDpi; |
| task.computeConfigResourceOverrides(inOutConfig, parentConfig); |
| |
| // screenLayout should honor override when both screenW/H are set. |
| assertTrue((inOutConfig.screenLayout & Configuration.SCREENLAYOUT_LONG_NO) != 0); |
| } |
| |
| @Test |
| public void testComputeNestedConfigResourceOverrides() { |
| final Task task = new TaskBuilder(mSupervisor).build(); |
| assertTrue(task.getResolvedOverrideBounds().isEmpty()); |
| int origScreenH = task.getConfiguration().screenHeightDp; |
| Configuration rootTaskConfig = new Configuration(); |
| rootTaskConfig.setTo(task.getRootTask().getRequestedOverrideConfiguration()); |
| rootTaskConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); |
| |
| // Set bounds on root task (not task) and verify that the task resource configuration |
| // changes despite it's override bounds being empty. |
| Rect bounds = new Rect(task.getRootTask().getBounds()); |
| bounds.bottom = (int) (bounds.bottom * 0.6f); |
| rootTaskConfig.windowConfiguration.setBounds(bounds); |
| task.getRootTask().onRequestedOverrideConfigurationChanged(rootTaskConfig); |
| assertNotEquals(origScreenH, task.getConfiguration().screenHeightDp); |
| } |
| |
| @Test |
| public void testFullScreenTaskNotAdjustedByMinimalSize() { |
| final Task fullscreenTask = new TaskBuilder(mSupervisor).build(); |
| final Rect originalTaskBounds = new Rect(fullscreenTask.getBounds()); |
| final ActivityInfo aInfo = new ActivityInfo(); |
| aInfo.windowLayout = new ActivityInfo.WindowLayout(0 /* width */, 0 /* widthFraction */, |
| 0 /* height */, 0 /* heightFraction */, 0 /* gravity */, |
| originalTaskBounds.width() * 2 /* minWidth */, |
| originalTaskBounds.height() * 2 /* minHeight */); |
| fullscreenTask.setMinDimensions(aInfo); |
| fullscreenTask.onConfigurationChanged(fullscreenTask.getParent().getConfiguration()); |
| |
| assertEquals(originalTaskBounds, fullscreenTask.getBounds()); |
| } |
| |
| @Test |
| public void testInsetDisregardedWhenFreeformOverlapsNavBar() { |
| TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea(); |
| Task rootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, |
| ACTIVITY_TYPE_STANDARD, true /* onTop */); |
| DisplayInfo displayInfo = new DisplayInfo(); |
| mAtm.mContext.getDisplay().getDisplayInfo(displayInfo); |
| final int displayHeight = displayInfo.logicalHeight; |
| final Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); |
| final Configuration inOutConfig = new Configuration(); |
| final Configuration parentConfig = new Configuration(); |
| final int longSide = 1200; |
| final int shortSide = 600; |
| parentConfig.densityDpi = 400; |
| parentConfig.screenHeightDp = 200; // 200 * 400 / 160 = 500px |
| parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px |
| parentConfig.windowConfiguration.setRotation(ROTATION_0); |
| |
| final int longSideDp = 480; // longSide / density = 1200 / 400 * 160 |
| final int shortSideDp = 240; // shortSide / density = 600 / 400 * 160 |
| final int screenLayout = parentConfig.screenLayout |
| & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK); |
| final int reducedScreenLayout = |
| Configuration.reduceScreenLayout(screenLayout, longSideDp, shortSideDp); |
| |
| // Portrait bounds overlapping with navigation bar, without insets. |
| final Rect freeformBounds = new Rect(0, |
| displayHeight - 10 - longSide, |
| shortSide, |
| displayHeight - 10); |
| inOutConfig.windowConfiguration.setBounds(freeformBounds); |
| // Set to freeform mode to verify bug fix. |
| inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); |
| |
| task.computeConfigResourceOverrides(inOutConfig, parentConfig); |
| |
| // screenW/H should not be effected by parent since overridden and freeform |
| assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi, |
| inOutConfig.screenWidthDp); |
| assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi, |
| inOutConfig.screenHeightDp); |
| assertEquals(reducedScreenLayout, inOutConfig.screenLayout); |
| |
| inOutConfig.setToDefaults(); |
| // Landscape bounds overlapping with navigtion bar, without insets. |
| freeformBounds.set(0, |
| displayHeight - 10 - shortSide, |
| longSide, |
| displayHeight - 10); |
| inOutConfig.windowConfiguration.setBounds(freeformBounds); |
| inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); |
| |
| task.computeConfigResourceOverrides(inOutConfig, parentConfig); |
| |
| assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi, |
| inOutConfig.screenWidthDp); |
| assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi, |
| inOutConfig.screenHeightDp); |
| assertEquals(reducedScreenLayout, inOutConfig.screenLayout); |
| } |
| |
| /** Ensures that the alias intent won't have target component resolved. */ |
| @Test |
| public void testTaskIntentActivityAlias() { |
| final String aliasClassName = DEFAULT_COMPONENT_PACKAGE_NAME + ".aliasActivity"; |
| final String targetClassName = DEFAULT_COMPONENT_PACKAGE_NAME + ".targetActivity"; |
| final ComponentName aliasComponent = |
| new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, aliasClassName); |
| final ComponentName targetComponent = |
| new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, targetClassName); |
| |
| final Intent intent = new Intent(); |
| intent.setComponent(aliasComponent); |
| final ActivityInfo info = new ActivityInfo(); |
| info.applicationInfo = new ApplicationInfo(); |
| info.packageName = DEFAULT_COMPONENT_PACKAGE_NAME; |
| info.targetActivity = targetClassName; |
| |
| final Task task = new Task.Builder(mAtm) |
| .setTaskId(1) |
| .setActivityInfo(info) |
| .setIntent(intent) |
| .build(); |
| assertEquals("The alias activity component should be saved in task intent.", aliasClassName, |
| task.intent.getComponent().getClassName()); |
| |
| ActivityRecord aliasActivity = new ActivityBuilder(mAtm).setComponent( |
| aliasComponent).setTargetActivity(targetClassName).build(); |
| assertEquals("Should be the same intent filter.", true, |
| task.isSameIntentFilter(aliasActivity)); |
| |
| ActivityRecord targetActivity = new ActivityBuilder(mAtm).setComponent( |
| targetComponent).build(); |
| assertEquals("Should be the same intent filter.", true, |
| task.isSameIntentFilter(targetActivity)); |
| |
| ActivityRecord defaultActivity = new ActivityBuilder(mAtm).build(); |
| assertEquals("Should not be the same intent filter.", false, |
| task.isSameIntentFilter(defaultActivity)); |
| } |
| |
| /** Test that root activity index is reported correctly for several activities in the task. */ |
| @Test |
| public void testFindRootIndex() { |
| final Task task = getTestTask(); |
| // Add an extra activity on top of the root one |
| new ActivityBuilder(mAtm).setTask(task).build(); |
| |
| assertEquals("The root activity in the task must be reported.", task.getChildAt(0), |
| task.getRootActivity( |
| true /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); |
| } |
| |
| /** |
| * Test that root activity index is reported correctly for several activities in the task when |
| * the activities on the bottom are finishing. |
| */ |
| @Test |
| public void testFindRootIndex_finishing() { |
| final Task task = getTestTask(); |
| // Add extra two activities and mark the two on the bottom as finishing. |
| final ActivityRecord activity0 = task.getBottomMostActivity(); |
| activity0.finishing = true; |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| activity1.finishing = true; |
| new ActivityBuilder(mAtm).setTask(task).build(); |
| |
| assertEquals("The first non-finishing activity in the task must be reported.", |
| task.getChildAt(2), task.getRootActivity( |
| true /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); |
| } |
| |
| /** |
| * Test that root activity index is reported correctly for several activities in the task when |
| * looking for the 'effective root'. |
| */ |
| @Test |
| public void testFindRootIndex_effectiveRoot() { |
| final Task task = getTestTask(); |
| // Add an extra activity on top of the root one |
| new ActivityBuilder(mAtm).setTask(task).build(); |
| |
| assertEquals("The root activity in the task must be reported.", |
| task.getChildAt(0), task.getRootActivity( |
| false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); |
| } |
| |
| /** |
| * Test that root activity index is reported correctly when looking for the 'effective root' in |
| * case when bottom activities are relinquishing task identity or finishing. |
| */ |
| @Test |
| public void testFindRootIndex_effectiveRoot_finishingAndRelinquishing() { |
| final ActivityRecord activity0 = new ActivityBuilder(mAtm).setCreateTask(true).build(); |
| final Task task = activity0.getTask(); |
| // Add extra two activities. Mark the one on the bottom with "relinquishTaskIdentity" and |
| // one above as finishing. |
| activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| activity1.finishing = true; |
| new ActivityBuilder(mAtm).setTask(task).build(); |
| |
| assertEquals("The first non-finishing activity and non-relinquishing task identity " |
| + "must be reported.", task.getChildAt(2), task.getRootActivity( |
| false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); |
| } |
| |
| /** |
| * Test that root activity index is reported correctly when looking for the 'effective root' |
| * for the case when there is only a single activity that also has relinquishTaskIdentity set. |
| */ |
| @Test |
| public void testFindRootIndex_effectiveRoot_relinquishingAndSingleActivity() { |
| final Task task = getTestTask(); |
| // Set relinquishTaskIdentity for the only activity in the task |
| task.getBottomMostActivity().info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; |
| |
| assertEquals("The root activity in the task must be reported.", |
| task.getChildAt(0), task.getRootActivity( |
| false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); |
| } |
| |
| /** |
| * Test that the topmost activity index is reported correctly when looking for the |
| * 'effective root' for the case when all activities have relinquishTaskIdentity set. |
| */ |
| @Test |
| public void testFindRootIndex_effectiveRoot_relinquishingMultipleActivities() { |
| final ActivityRecord activity0 = new ActivityBuilder(mAtm).setCreateTask(true).build(); |
| final Task task = activity0.getTask(); |
| // Set relinquishTaskIdentity for all activities in the task |
| activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; |
| |
| assertEquals("The topmost activity in the task must be reported.", |
| task.getChildAt(task.getChildCount() - 1), task.getRootActivity( |
| false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); |
| } |
| |
| /** Test that bottom-most activity is reported in {@link Task#getRootActivity()}. */ |
| @Test |
| public void testGetRootActivity() { |
| final Task task = getTestTask(); |
| // Add an extra activity on top of the root one |
| new ActivityBuilder(mAtm).setTask(task).build(); |
| |
| assertEquals("The root activity in the task must be reported.", |
| task.getBottomMostActivity(), task.getRootActivity()); |
| } |
| |
| /** |
| * Test that first non-finishing activity is reported in {@link Task#getRootActivity()}. |
| */ |
| @Test |
| public void testGetRootActivity_finishing() { |
| final Task task = getTestTask(); |
| // Add an extra activity on top of the root one |
| new ActivityBuilder(mAtm).setTask(task).build(); |
| // Mark the root as finishing |
| task.getBottomMostActivity().finishing = true; |
| |
| assertEquals("The first non-finishing activity in the task must be reported.", |
| task.getChildAt(1), task.getRootActivity()); |
| } |
| |
| /** |
| * Test that relinquishTaskIdentity flag is ignored in {@link Task#getRootActivity()}. |
| */ |
| @Test |
| public void testGetRootActivity_relinquishTaskIdentity() { |
| final Task task = getTestTask(); |
| // Mark the bottom-most activity with FLAG_RELINQUISH_TASK_IDENTITY. |
| final ActivityRecord activity0 = task.getBottomMostActivity(); |
| activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; |
| // Add an extra activity on top of the root one. |
| new ActivityBuilder(mAtm).setTask(task).build(); |
| |
| assertEquals("The root activity in the task must be reported.", |
| task.getBottomMostActivity(), task.getRootActivity()); |
| } |
| |
| /** |
| * Test that no activity is reported in {@link Task#getRootActivity()} when all activities |
| * in the task are finishing. |
| */ |
| @Test |
| public void testGetRootActivity_allFinishing() { |
| final Task task = getTestTask(); |
| // Mark the bottom-most activity as finishing. |
| final ActivityRecord activity0 = task.getBottomMostActivity(); |
| activity0.finishing = true; |
| // Add an extra activity on top of the root one and mark it as finishing |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| activity1.finishing = true; |
| |
| assertNull("No activity must be reported if all are finishing", task.getRootActivity()); |
| } |
| |
| /** |
| * Test that first non-finishing activity is the root of task. |
| */ |
| @Test |
| public void testIsRootActivity() { |
| final Task task = getTestTask(); |
| // Mark the bottom-most activity as finishing. |
| final ActivityRecord activity0 = task.getBottomMostActivity(); |
| activity0.finishing = true; |
| // Add an extra activity on top of the root one. |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| |
| assertFalse("Finishing activity must not be the root of task", activity0.isRootOfTask()); |
| assertTrue("Non-finishing activity must be the root of task", activity1.isRootOfTask()); |
| } |
| |
| /** |
| * Test that if all activities in the task are finishing, then the one on the bottom is the |
| * root of task. |
| */ |
| @Test |
| public void testIsRootActivity_allFinishing() { |
| final Task task = getTestTask(); |
| // Mark the bottom-most activity as finishing. |
| final ActivityRecord activity0 = task.getBottomMostActivity(); |
| activity0.finishing = true; |
| // Add an extra activity on top of the root one and mark it as finishing |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| activity1.finishing = true; |
| |
| assertTrue("Bottom activity must be the root of task", activity0.isRootOfTask()); |
| assertFalse("Finishing activity on top must not be the root of task", |
| activity1.isRootOfTask()); |
| } |
| |
| /** |
| * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)}. |
| */ |
| @Test |
| public void testGetTaskForActivity() { |
| final Task task0 = getTestTask(); |
| final ActivityRecord activity0 = task0.getBottomMostActivity(); |
| |
| final Task task1 = getTestTask(); |
| final ActivityRecord activity1 = task1.getBottomMostActivity(); |
| |
| assertEquals(task0.mTaskId, |
| ActivityRecord.getTaskForActivityLocked(activity0.token, false /* onlyRoot */)); |
| assertEquals(task1.mTaskId, |
| ActivityRecord.getTaskForActivityLocked(activity1.token, false /* onlyRoot */)); |
| } |
| |
| /** |
| * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with finishing |
| * activity. |
| */ |
| @Test |
| public void testGetTaskForActivity_onlyRoot_finishing() { |
| final Task task = getTestTask(); |
| // Make the current root activity finishing |
| final ActivityRecord activity0 = task.getBottomMostActivity(); |
| activity0.finishing = true; |
| // Add an extra activity on top - this will be the new root |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| // Add one more on top |
| final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); |
| |
| assertEquals(task.mTaskId, |
| ActivityRecord.getTaskForActivityLocked(activity0.token, true /* onlyRoot */)); |
| assertEquals(task.mTaskId, |
| ActivityRecord.getTaskForActivityLocked(activity1.token, true /* onlyRoot */)); |
| assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID, |
| ActivityRecord.getTaskForActivityLocked(activity2.token, true /* onlyRoot */)); |
| } |
| |
| /** |
| * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with activity that |
| * relinquishes task identity. |
| */ |
| @Test |
| public void testGetTaskForActivity_onlyRoot_relinquishTaskIdentity() { |
| final ActivityRecord activity0 = new ActivityBuilder(mAtm).setCreateTask(true).build(); |
| final Task task = activity0.getTask(); |
| // Make the current root activity relinquish task identity |
| activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; |
| // Add an extra activity on top - this will be the new root |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| // Add one more on top |
| final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); |
| |
| assertEquals(task.mTaskId, |
| ActivityRecord.getTaskForActivityLocked(activity0.token, true /* onlyRoot */)); |
| assertEquals(task.mTaskId, |
| ActivityRecord.getTaskForActivityLocked(activity1.token, true /* onlyRoot */)); |
| assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID, |
| ActivityRecord.getTaskForActivityLocked(activity2.token, true /* onlyRoot */)); |
| } |
| |
| /** |
| * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} allowing non-root |
| * entries. |
| */ |
| @Test |
| public void testGetTaskForActivity_notOnlyRoot() { |
| final Task task = getTestTask(); |
| // Mark the bottom-most activity as finishing. |
| final ActivityRecord activity0 = task.getBottomMostActivity(); |
| activity0.finishing = true; |
| |
| // Add an extra activity on top of the root one and make it relinquish task identity |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; |
| |
| // Add one more activity on top |
| final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); |
| |
| assertEquals(task.mTaskId, |
| ActivityRecord.getTaskForActivityLocked(activity0.token, false /* onlyRoot */)); |
| assertEquals(task.mTaskId, |
| ActivityRecord.getTaskForActivityLocked(activity1.token, false /* onlyRoot */)); |
| assertEquals(task.mTaskId, |
| ActivityRecord.getTaskForActivityLocked(activity2.token, false /* onlyRoot */)); |
| } |
| |
| /** |
| * Test {@link Task#updateEffectiveIntent()}. |
| */ |
| @Test |
| public void testUpdateEffectiveIntent() { |
| // Test simple case with a single activity. |
| final Task task = getTestTask(); |
| final ActivityRecord activity0 = task.getBottomMostActivity(); |
| |
| spyOn(task); |
| task.updateEffectiveIntent(); |
| verify(task).setIntent(eq(activity0)); |
| } |
| |
| /** |
| * Test {@link Task#updateEffectiveIntent()} with root activity marked as finishing. This |
| * should make the task use the second activity when updating the intent. |
| */ |
| @Test |
| public void testUpdateEffectiveIntent_rootFinishing() { |
| // Test simple case with a single activity. |
| final Task task = getTestTask(); |
| final ActivityRecord activity0 = task.getBottomMostActivity(); |
| // Mark the bottom-most activity as finishing. |
| activity0.finishing = true; |
| // Add an extra activity on top of the root one |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| |
| spyOn(task); |
| task.updateEffectiveIntent(); |
| verify(task).setIntent(eq(activity1)); |
| } |
| |
| /** |
| * Test {@link Task#updateEffectiveIntent()} when all activities are finishing or |
| * relinquishing task identity. In this case the root activity should still be used when |
| * updating the intent (legacy behavior). |
| */ |
| @Test |
| public void testUpdateEffectiveIntent_allFinishing() { |
| // Test simple case with a single activity. |
| final Task task = getTestTask(); |
| final ActivityRecord activity0 = task.getBottomMostActivity(); |
| // Mark the bottom-most activity as finishing. |
| activity0.finishing = true; |
| // Add an extra activity on top of the root one and make it relinquish task identity |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| activity1.finishing = true; |
| |
| // Task must still update the intent using the root activity (preserving legacy behavior). |
| spyOn(task); |
| task.updateEffectiveIntent(); |
| verify(task).setIntent(eq(activity0)); |
| } |
| |
| /** |
| * Test {@link Task#updateEffectiveIntent()} when activity with relinquishTaskIdentity but |
| * another with different uid. This should make the task use the root activity when updating the |
| * intent. |
| */ |
| @Test |
| public void testUpdateEffectiveIntent_relinquishingWithDifferentUid() { |
| final ActivityRecord activity0 = new ActivityBuilder(mAtm) |
| .setActivityFlags(FLAG_RELINQUISH_TASK_IDENTITY).setCreateTask(true).build(); |
| final Task task = activity0.getTask(); |
| |
| // Add an extra activity on top |
| new ActivityBuilder(mAtm).setUid(11).setTask(task).build(); |
| |
| spyOn(task); |
| task.updateEffectiveIntent(); |
| verify(task).setIntent(eq(activity0)); |
| } |
| |
| /** |
| * Test {@link Task#updateEffectiveIntent()} with activities set as relinquishTaskIdentity. |
| * This should make the task use the topmost activity when updating the intent. |
| */ |
| @Test |
| public void testUpdateEffectiveIntent_relinquishingMultipleActivities() { |
| final ActivityRecord activity0 = new ActivityBuilder(mAtm) |
| .setActivityFlags(FLAG_RELINQUISH_TASK_IDENTITY).setCreateTask(true).build(); |
| final Task task = activity0.getTask(); |
| // Add an extra activity on top |
| final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); |
| activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; |
| |
| // Add an extra activity on top |
| final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); |
| |
| spyOn(task); |
| task.updateEffectiveIntent(); |
| verify(task).setIntent(eq(activity2)); |
| } |
| |
| @Test |
| public void testSaveLaunchingStateWhenConfigurationChanged() { |
| LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; |
| spyOn(persister); |
| |
| final Task task = getTestTask(); |
| task.setHasBeenVisible(false); |
| task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); |
| task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); |
| |
| task.setHasBeenVisible(true); |
| task.onConfigurationChanged(task.getParent().getConfiguration()); |
| |
| verify(persister).saveTask(task, task.getDisplayContent()); |
| } |
| |
| @Test |
| public void testSaveLaunchingStateWhenClearingParent() { |
| LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; |
| spyOn(persister); |
| |
| final Task task = getTestTask(); |
| task.setHasBeenVisible(false); |
| task.getDisplayContent().setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); |
| task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); |
| final DisplayContent oldDisplay = task.getDisplayContent(); |
| |
| LaunchParamsController.LaunchParams params = new LaunchParamsController.LaunchParams(); |
| params.mWindowingMode = WINDOWING_MODE_UNDEFINED; |
| persister.getLaunchParams(task, null, params); |
| assertEquals(WINDOWING_MODE_UNDEFINED, params.mWindowingMode); |
| |
| task.setHasBeenVisible(true); |
| task.removeImmediately(); |
| |
| verify(persister).saveTask(task, oldDisplay); |
| |
| persister.getLaunchParams(task, null, params); |
| assertEquals(WINDOWING_MODE_FULLSCREEN, params.mWindowingMode); |
| } |
| |
| @Test |
| public void testNotSaveLaunchingStateNonFreeformDisplay() { |
| LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; |
| spyOn(persister); |
| |
| final Task task = getTestTask(); |
| task.setHasBeenVisible(false); |
| task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); |
| |
| task.setHasBeenVisible(true); |
| task.onConfigurationChanged(task.getParent().getConfiguration()); |
| |
| Mockito.verify(persister, never()).saveTask(same(task), any()); |
| } |
| |
| @Test |
| public void testNotSaveLaunchingStateWhenNotFullscreenOrFreeformWindow() { |
| LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; |
| spyOn(persister); |
| |
| final Task task = getTestTask(); |
| task.setHasBeenVisible(false); |
| task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); |
| task.getRootTask().setWindowingMode(WINDOWING_MODE_PINNED); |
| |
| task.setHasBeenVisible(true); |
| task.onConfigurationChanged(task.getParent().getConfiguration()); |
| |
| Mockito.verify(persister, never()).saveTask(same(task), any()); |
| } |
| |
| @Test |
| public void testNotSaveLaunchingStateForNonLeafTask() { |
| LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; |
| spyOn(persister); |
| |
| final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true) |
| .setCreateParentTask(true).build().getRootTask(); |
| task.setHasBeenVisible(false); |
| task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); |
| task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); |
| |
| final Task leafTask = createTaskInRootTask(task, 0 /* userId */); |
| |
| leafTask.setHasBeenVisible(true); |
| task.setHasBeenVisible(true); |
| task.onConfigurationChanged(task.getParent().getConfiguration()); |
| |
| Mockito.verify(persister, never()).saveTask(same(task), any()); |
| verify(persister).saveTask(same(leafTask), any()); |
| } |
| |
| @Test |
| public void testNotSpecifyOrientationByFloatingTask() { |
| final Task task = new TaskBuilder(mSupervisor) |
| .setCreateActivity(true).setCreateParentTask(true).build(); |
| final ActivityRecord activity = task.getTopMostActivity(); |
| final WindowContainer<?> parentContainer = task.getParent(); |
| final TaskDisplayArea taskDisplayArea = task.getDisplayArea(); |
| activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); |
| |
| assertEquals(SCREEN_ORIENTATION_LANDSCAPE, parentContainer.getOrientation()); |
| assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation()); |
| |
| task.setWindowingMode(WINDOWING_MODE_PINNED); |
| |
| // TDA returns the last orientation when child returns UNSET |
| assertEquals(SCREEN_ORIENTATION_UNSET, parentContainer.getOrientation()); |
| assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation()); |
| } |
| |
| @Test |
| public void testNotSpecifyOrientation_taskDisplayAreaNotFocused() { |
| final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); |
| final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( |
| mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea", |
| FEATURE_VENDOR_FIRST); |
| final Task firstRootTask = firstTaskDisplayArea.createRootTask( |
| WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); |
| final Task secondRootTask = secondTaskDisplayArea.createRootTask( |
| WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); |
| final ActivityRecord firstActivity = new ActivityBuilder(mAtm) |
| .setTask(firstRootTask).build(); |
| final ActivityRecord secondActivity = new ActivityBuilder(mAtm) |
| .setTask(secondRootTask).build(); |
| firstActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); |
| secondActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); |
| |
| // Activity on TDA1 is focused |
| mDisplayContent.setFocusedApp(firstActivity); |
| |
| assertEquals(SCREEN_ORIENTATION_LANDSCAPE, firstTaskDisplayArea.getOrientation()); |
| assertEquals(SCREEN_ORIENTATION_UNSET, secondTaskDisplayArea.getOrientation()); |
| |
| // No focused app, TDA1 is still recorded as last focused. |
| mDisplayContent.setFocusedApp(null); |
| |
| assertEquals(SCREEN_ORIENTATION_LANDSCAPE, firstTaskDisplayArea.getOrientation()); |
| assertEquals(SCREEN_ORIENTATION_UNSET, secondTaskDisplayArea.getOrientation()); |
| |
| // Activity on TDA2 is focused |
| mDisplayContent.setFocusedApp(secondActivity); |
| |
| assertEquals(SCREEN_ORIENTATION_UNSET, firstTaskDisplayArea.getOrientation()); |
| assertEquals(SCREEN_ORIENTATION_PORTRAIT, secondTaskDisplayArea.getOrientation()); |
| } |
| |
| @Test |
| public void testTaskOrientationOnDisplayWindowingModeChange() { |
| // Skip unnecessary operations to speed up the test. |
| mAtm.deferWindowLayout(); |
| final Task task = getTestTask(); |
| final ActivityRecord activity = task.getTopMostActivity(); |
| final DisplayContent display = task.getDisplayContent(); |
| mWm.setWindowingMode(display.mDisplayId, WINDOWING_MODE_FREEFORM); |
| |
| activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); |
| assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation()); |
| assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, display.getLastOrientation()); |
| |
| mWm.setWindowingMode(display.mDisplayId, WINDOWING_MODE_FULLSCREEN); |
| assertEquals(SCREEN_ORIENTATION_LANDSCAPE, task.getOrientation()); |
| assertEquals(SCREEN_ORIENTATION_LANDSCAPE, display.getLastOrientation()); |
| assertEquals(Configuration.ORIENTATION_LANDSCAPE, display.getConfiguration().orientation); |
| } |
| |
| @Test |
| public void testGetNonNullDimmerOnUntrustedDisplays() { |
| final DisplayInfo untrustedDisplayInfo = new DisplayInfo(mDisplayInfo); |
| untrustedDisplayInfo.flags &= ~Display.FLAG_TRUSTED; |
| final DisplayContent untrustedDisplay = createNewDisplay(untrustedDisplayInfo); |
| final ActivityRecord activity = createActivityRecord(untrustedDisplay); |
| activity.setOccludesParent(false); |
| assertNotNull(activity.getTask().getDimmer()); |
| } |
| |
| @Test |
| public void testResumeTask_doNotResumeTaskFragmentBehindTranslucent() { |
| final Task task = createTask(mDisplayContent); |
| final TaskFragment tfBehind = createTaskFragmentWithParentTask( |
| task, false /* createEmbeddedTask */); |
| final TaskFragment tfFront = createTaskFragmentWithParentTask( |
| task, false /* createEmbeddedTask */); |
| spyOn(tfFront); |
| doReturn(true).when(tfFront).isTranslucent(any()); |
| |
| // TaskFragment behind another translucent TaskFragment should not be resumed. |
| assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, |
| tfBehind.getVisibility(null /* starting */)); |
| assertTrue(tfBehind.isFocusable()); |
| assertFalse(tfBehind.canBeResumed(null /* starting */)); |
| |
| spyOn(tfBehind); |
| task.resumeTopActivityUncheckedLocked(null /* prev */, ActivityOptions.makeBasic(), |
| false /* deferPause */); |
| |
| verify(tfBehind, never()).resumeTopActivity(any(), any(), anyBoolean()); |
| } |
| |
| private Task getTestTask() { |
| final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); |
| return task.getBottomMostTask(); |
| } |
| |
| private void testRootTaskBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds, |
| Rect expectedConfigBounds) { |
| |
| TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea(); |
| Task rootTask = taskDisplayArea.createRootTask(windowingMode, ACTIVITY_TYPE_STANDARD, |
| true /* onTop */); |
| Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); |
| |
| final Configuration parentConfig = rootTask.getConfiguration(); |
| parentConfig.windowConfiguration.setAppBounds(parentBounds); |
| task.setBounds(bounds); |
| |
| task.resolveOverrideConfiguration(parentConfig); |
| // Assert that both expected and actual are null or are equal to each other |
| assertEquals(expectedConfigBounds, |
| task.getResolvedOverrideConfiguration().windowConfiguration.getAppBounds()); |
| } |
| |
| private byte[] serializeToBytes(Task r) throws Exception { |
| try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { |
| final TypedXmlSerializer serializer = Xml.newFastSerializer(); |
| serializer.setOutput(os, "UTF-8"); |
| serializer.startDocument(null, true); |
| serializer.startTag(null, TASK_TAG); |
| r.saveToXml(serializer); |
| serializer.endTag(null, TASK_TAG); |
| serializer.endDocument(); |
| |
| os.flush(); |
| return os.toByteArray(); |
| } |
| } |
| |
| private Task restoreFromBytes(byte[] in) throws IOException, XmlPullParserException { |
| try (Reader reader = new InputStreamReader(new ByteArrayInputStream(in))) { |
| final TypedXmlPullParser parser = Xml.newFastPullParser(); |
| parser.setInput(reader); |
| assertEquals(XmlPullParser.START_TAG, parser.next()); |
| assertEquals(TASK_TAG, parser.getName()); |
| return Task.restoreFromXml(parser, mAtm.mTaskSupervisor); |
| } |
| } |
| |
| private Task createTask(int taskId) { |
| return new Task.Builder(mAtm) |
| .setTaskId(taskId) |
| .setIntent(new Intent()) |
| .setRealActivity(ActivityBuilder.getDefaultComponent()) |
| .setEffectiveUid(10050) |
| .buildInner(); |
| } |
| } |