1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.am;
18
19import android.content.pm.ActivityInfo.WindowLayout;
20import android.graphics.Rect;
21import android.platform.test.annotations.Presubmit;
22import android.support.test.filters.MediumTest;
23import android.support.test.runner.AndroidJUnit4;
24
25import android.view.Gravity;
26
27import org.junit.runner.RunWith;
28import org.junit.Before;
29import org.junit.Test;
30
31import org.mockito.invocation.InvocationOnMock;
32
33import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
34import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
35
36import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
37
38import static org.mockito.Mockito.any;
39import static org.mockito.Mockito.mock;
40import static org.mockito.Mockito.when;
41import static org.mockito.Mockito.doAnswer;
42import static org.junit.Assert.assertEquals;
43
44
45/**
46 * Tests for exercising resizing task bounds.
47 *
48 * Build/Install/Run:
49 *  atest FrameworksServicesTests:TaskLaunchParamsModifierTests
50 */
51@MediumTest
52@Presubmit
53@RunWith(AndroidJUnit4.class)
54public class TaskLaunchParamsModifierTests extends ActivityTestsBase {
55    private final static int STACK_WIDTH = 100;
56    private final static int STACK_HEIGHT = 200;
57
58    private final static Rect STACK_BOUNDS = new Rect(0, 0, STACK_WIDTH, STACK_HEIGHT);
59
60    private ActivityManagerService mService;
61    private ActivityStack mStack;
62    private TaskRecord mTask;
63
64    private TaskLaunchParamsModifier mPositioner;
65
66    private LaunchParamsController.LaunchParams mCurrent;
67    private LaunchParamsController.LaunchParams mResult;
68
69    @Before
70    @Override
71    public void setUp() throws Exception {
72        super.setUp();
73
74        mService = createActivityManagerService();
75        mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
76                WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
77        mStack.requestResize(STACK_BOUNDS);
78
79        // We must create the task after resizing to make sure it does not inherit the stack
80        // dimensions on resize.
81        mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
82
83        mPositioner = new TaskLaunchParamsModifier();
84
85        mResult = new LaunchParamsController.LaunchParams();
86        mCurrent = new LaunchParamsController.LaunchParams();
87    }
88
89    /**
90     * Ensures that the setup bounds are set as expected with the stack bounds set and the task
91     * bounds still {@code null}.
92     * @throws Exception
93     */
94    @Test
95    public void testInitialBounds() throws Exception {
96        assertEquals(mStack.getOverrideBounds(), STACK_BOUNDS);
97        assertEquals(mTask.getOverrideBounds(), new Rect());
98    }
99
100    /**
101     * Ensures that a task positioned with no {@link WindowLayout} receives the default launch
102     * position.
103     * @throws Exception
104     */
105    @Test
106    public void testLaunchNoWindowLayout() throws Exception {
107        assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask, null /*layout*/,
108                null /*record*/, null /*source*/, null /*options*/, mCurrent, mResult));
109        assertEquals(getDefaultBounds(Gravity.NO_GRAVITY), mResult.mBounds);
110    }
111
112    /**
113     * Ensures that a task positioned with an empty {@link WindowLayout} receives the default launch
114     * position.
115     * @throws Exception
116     */
117    @Test
118    public void testlaunchEmptyWindowLayout() throws Exception {
119        assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask,
120                new WindowLayout(0, 0, 0, 0, Gravity.NO_GRAVITY, 0, 0), null /*activity*/,
121                null /*source*/, null /*options*/, mCurrent, mResult));
122        assertEquals(mResult.mBounds, getDefaultBounds(Gravity.NO_GRAVITY));
123    }
124
125    /**
126     * Ensures that a task positioned with a {@link WindowLayout} gravity specified is positioned
127     * according to specification.
128     * @throws Exception
129     */
130    @Test
131    public void testlaunchWindowLayoutGravity() throws Exception {
132        // Unspecified gravity should be ignored
133        testGravity(Gravity.NO_GRAVITY);
134
135        // Unsupported gravity should be ignored
136        testGravity(Gravity.LEFT);
137        testGravity(Gravity.RIGHT);
138
139        // Test defaults for vertical gravities
140        testGravity(Gravity.TOP);
141        testGravity(Gravity.BOTTOM);
142
143        // Test corners
144        testGravity(Gravity.TOP | Gravity.LEFT);
145        testGravity(Gravity.TOP | Gravity.RIGHT);
146        testGravity(Gravity.BOTTOM | Gravity.LEFT);
147        testGravity(Gravity.BOTTOM | Gravity.RIGHT);
148    }
149
150    private void testGravity(int gravity) {
151        try {
152            assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask,
153                    new WindowLayout(0, 0, 0, 0, gravity, 0, 0), null /*activity*/,
154                    null /*source*/, null /*options*/, mCurrent, mResult));
155            assertEquals(mResult.mBounds, getDefaultBounds(gravity));
156        } finally {
157            mCurrent.reset();
158            mResult.reset();
159        }
160    }
161
162    /**
163     * Ensures that a task which causes a conflict with another task when positioned is adjusted as
164     * expected.
165     * @throws Exception
166     */
167    @Test
168    public void testLaunchWindowCenterConflict() throws Exception {
169        testConflict(Gravity.NO_GRAVITY);
170        testConflict(Gravity.TOP);
171        testConflict(Gravity.BOTTOM);
172        testConflict(Gravity.TOP | Gravity.LEFT);
173        testConflict(Gravity.TOP | Gravity.RIGHT);
174        testConflict(Gravity.BOTTOM | Gravity.LEFT);
175        testConflict(Gravity.BOTTOM | Gravity.RIGHT);
176    }
177
178    private void testConflict(int gravity) {
179        final WindowLayout layout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0);
180
181        // layout first task
182        mService.mStackSupervisor.getLaunchParamsController().layoutTask(mTask, layout);
183
184        // Second task will be laid out on top of the first so starting bounds is the same.
185        final Rect expectedBounds = new Rect(mTask.getOverrideBounds());
186
187        ActivityRecord activity = null;
188        TaskRecord secondTask = null;
189
190        // wrap with try/finally to ensure cleanup of activity/stack.
191        try {
192            // empty tasks are ignored in conflicts
193            activity = new ActivityBuilder(mService).setTask(mTask).build();
194
195            // Create secondary task
196            secondTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
197
198            // layout second task
199            assertEquals(RESULT_CONTINUE,
200                    mPositioner.onCalculate(secondTask, layout, null /*activity*/,
201                            null /*source*/, null /*options*/, mCurrent, mResult));
202
203            if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)
204                    || (gravity & (Gravity.BOTTOM | Gravity.RIGHT))
205                    == (Gravity.BOTTOM | Gravity.RIGHT)) {
206                expectedBounds.offset(-TaskLaunchParamsModifier.getHorizontalStep(
207                        mStack.getOverrideBounds()), 0);
208            } else if ((gravity & Gravity.TOP) == Gravity.TOP
209                    || (gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
210                expectedBounds.offset(
211                        TaskLaunchParamsModifier.getHorizontalStep(mStack.getOverrideBounds()), 0);
212            } else {
213                expectedBounds.offset(
214                        TaskLaunchParamsModifier.getHorizontalStep(mStack.getOverrideBounds()),
215                        TaskLaunchParamsModifier.getVerticalStep(mStack.getOverrideBounds()));
216            }
217
218            assertEquals(mResult.mBounds, expectedBounds);
219        } finally {
220            // Remove task and activity to prevent influencing future tests
221            if (activity != null) {
222                mTask.removeActivity(activity);
223            }
224
225            if (secondTask != null) {
226                mStack.removeTask(secondTask, "cleanup", ActivityStack.REMOVE_TASK_MODE_DESTROYING);
227            }
228        }
229    }
230
231    private Rect getDefaultBounds(int gravity) {
232        final Rect bounds = new Rect();
233        bounds.set(mStack.getOverrideBounds());
234
235        final int verticalInset =
236                TaskLaunchParamsModifier.getFreeformStartTop(mStack.getOverrideBounds());
237        final int horizontalInset =
238                TaskLaunchParamsModifier.getFreeformStartLeft(mStack.getOverrideBounds());
239
240        bounds.inset(horizontalInset, verticalInset);
241
242        if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)) {
243            bounds.offsetTo(horizontalInset * 2, 0);
244        } else if ((gravity & Gravity.TOP) == Gravity.TOP) {
245            bounds.offsetTo(0, 0);
246        } else if ((gravity & (Gravity.BOTTOM | Gravity.RIGHT))
247                == (Gravity.BOTTOM | Gravity.RIGHT)) {
248            bounds.offsetTo(horizontalInset * 2, verticalInset * 2);
249        } else if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
250            bounds.offsetTo(0, verticalInset * 2);
251        }
252
253        return bounds;
254    }
255}
256