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.policy;
18
19import static android.view.Surface.ROTATION_0;
20import static android.view.Surface.ROTATION_180;
21import static android.view.Surface.ROTATION_270;
22import static android.view.Surface.ROTATION_90;
23import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
24import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
25import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
26
27import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
28
29import static org.junit.Assert.assertEquals;
30import static org.mockito.Mockito.mock;
31
32import android.content.Context;
33import android.content.ContextWrapper;
34import android.content.pm.PackageManager;
35import android.content.res.Resources;
36import android.graphics.Matrix;
37import android.graphics.Path;
38import android.graphics.PixelFormat;
39import android.graphics.Rect;
40import android.os.IBinder;
41import android.os.UserHandle;
42import android.support.test.InstrumentationRegistry;
43import android.testing.TestableResources;
44import android.util.Pair;
45import android.view.Display;
46import android.view.DisplayCutout;
47import android.view.DisplayInfo;
48import android.view.Gravity;
49import android.view.View;
50import android.view.WindowManager;
51import android.view.WindowManagerGlobal;
52import android.view.accessibility.AccessibilityManager;
53import android.view.accessibility.IAccessibilityManager;
54
55import com.android.server.policy.keyguard.KeyguardServiceDelegate;
56import com.android.server.wm.DisplayFrames;
57import com.android.server.wm.utils.WmDisplayCutout;
58
59import org.junit.Before;
60
61public class PhoneWindowManagerTestBase {
62    static final int DISPLAY_WIDTH = 500;
63    static final int DISPLAY_HEIGHT = 1000;
64
65    static final int STATUS_BAR_HEIGHT = 10;
66    static final int NAV_BAR_HEIGHT = 15;
67    static final int DISPLAY_CUTOUT_HEIGHT = 8;
68
69    TestablePhoneWindowManager mPolicy;
70    TestContextWrapper mContext;
71    DisplayFrames mFrames;
72
73    FakeWindowState mStatusBar;
74    FakeWindowState mNavigationBar;
75    private boolean mHasDisplayCutout;
76    private int mRotation = ROTATION_0;
77
78    @Before
79    public void setUpBase() throws Exception {
80        mContext = new TestContextWrapper(InstrumentationRegistry.getTargetContext());
81        mContext.getResourceMocker().addOverride(
82                com.android.internal.R.dimen.status_bar_height_portrait, STATUS_BAR_HEIGHT);
83        mContext.getResourceMocker().addOverride(
84                com.android.internal.R.dimen.status_bar_height_landscape, STATUS_BAR_HEIGHT);
85        mContext.getResourceMocker().addOverride(
86                com.android.internal.R.dimen.navigation_bar_height, NAV_BAR_HEIGHT);
87        mContext.getResourceMocker().addOverride(
88                com.android.internal.R.dimen.navigation_bar_height_landscape, NAV_BAR_HEIGHT);
89        mContext.getResourceMocker().addOverride(
90                com.android.internal.R.dimen.navigation_bar_width, NAV_BAR_HEIGHT);
91
92        mPolicy = TestablePhoneWindowManager.create(mContext);
93
94        updateDisplayFrames();
95    }
96
97    public void setRotation(int rotation) {
98        mRotation = rotation;
99        updateDisplayFrames();
100    }
101
102    private void updateDisplayFrames() {
103        Pair<DisplayInfo, WmDisplayCutout> info = displayInfoAndCutoutForRotation(mRotation,
104                mHasDisplayCutout);
105        mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info.first, info.second);
106    }
107
108    public void addStatusBar() {
109        mStatusBar = new FakeWindowState();
110        mStatusBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, STATUS_BAR_HEIGHT,
111                TYPE_STATUS_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT);
112        mStatusBar.attrs.gravity = Gravity.TOP;
113
114        mPolicy.addWindow(mStatusBar);
115        mPolicy.mLastSystemUiFlags |= View.STATUS_BAR_TRANSPARENT;
116    }
117
118    public void addNavigationBar() {
119        mNavigationBar = new FakeWindowState();
120        mNavigationBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, NAV_BAR_HEIGHT,
121                TYPE_NAVIGATION_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT);
122        mNavigationBar.attrs.gravity = Gravity.BOTTOM;
123
124        mPolicy.addWindow(mNavigationBar);
125        mPolicy.mHasNavigationBar = true;
126        mPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT;
127    }
128
129    public void addDisplayCutout() {
130        mHasDisplayCutout = true;
131        updateDisplayFrames();
132    }
133
134    /** Asserts that {@code actual} is inset by the given amounts from the full display rect. */
135    public void assertInsetBy(Rect actual, int expectedInsetLeft, int expectedInsetTop,
136            int expectedInsetRight, int expectedInsetBottom) {
137        assertEquals(new Rect(expectedInsetLeft, expectedInsetTop,
138                mFrames.mDisplayWidth - expectedInsetRight,
139                mFrames.mDisplayHeight - expectedInsetBottom), actual);
140    }
141
142    /**
143     * Asserts that {@code actual} is inset by the given amounts from the full display rect.
144     *
145     * Convenience wrapper for when only the top and bottom inset are non-zero.
146     */
147    public void assertInsetByTopBottom(Rect actual, int expectedInsetTop, int expectedInsetBottom) {
148        assertInsetBy(actual, 0, expectedInsetTop, 0, expectedInsetBottom);
149    }
150
151    public static DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) {
152        return displayInfoAndCutoutForRotation(rotation, withDisplayCutout).first;
153    }
154    public static Pair<DisplayInfo, WmDisplayCutout> displayInfoAndCutoutForRotation(int rotation,
155            boolean withDisplayCutout) {
156        DisplayInfo info = new DisplayInfo();
157        WmDisplayCutout cutout = null;
158
159        final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270;
160        info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
161        info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
162        info.rotation = rotation;
163        if (withDisplayCutout) {
164            cutout = WmDisplayCutout.computeSafeInsets(
165                    displayCutoutForRotation(rotation), info.logicalWidth,
166                    info.logicalHeight);
167            info.displayCutout = cutout.getDisplayCutout();
168        } else {
169            info.displayCutout = null;
170        }
171        return Pair.create(info, cutout);
172    }
173
174    private static DisplayCutout displayCutoutForRotation(int rotation) {
175        Path p = new Path();
176        p.addRect(DISPLAY_WIDTH / 4, 0, DISPLAY_WIDTH * 3 / 4, DISPLAY_CUTOUT_HEIGHT,
177                Path.Direction.CCW);
178
179        Matrix m = new Matrix();
180        transformPhysicalToLogicalCoordinates(rotation, DISPLAY_WIDTH, DISPLAY_HEIGHT, m);
181        p.transform(m);
182
183        return DisplayCutout.fromBounds(p);
184    }
185
186    static class TestContextWrapper extends ContextWrapper {
187        private final TestableResources mResourceMocker;
188
189        public TestContextWrapper(Context targetContext) {
190            super(targetContext);
191            mResourceMocker = new TestableResources(targetContext.getResources());
192        }
193
194        @Override
195        public int checkPermission(String permission, int pid, int uid) {
196            return PackageManager.PERMISSION_GRANTED;
197        }
198
199        @Override
200        public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
201            return PackageManager.PERMISSION_GRANTED;
202        }
203
204        @Override
205        public Resources getResources() {
206            return mResourceMocker.getResources();
207        }
208
209        public TestableResources getResourceMocker() {
210            return mResourceMocker;
211        }
212    }
213
214    static class TestablePhoneWindowManager extends PhoneWindowManager {
215
216        public TestablePhoneWindowManager() {
217        }
218
219        @Override
220        void initializeHdmiState() {
221            // Do nothing.
222        }
223
224        @Override
225        Context getSystemUiContext() {
226            return mContext;
227        }
228
229        void addWindow(WindowState state) {
230            if (state instanceof FakeWindowState) {
231                ((FakeWindowState) state).surfaceLayer =
232                        getWindowLayerFromTypeLw(state.getAttrs().type,
233                                true /* canAddInternalSystemWindow */);
234            }
235            adjustWindowParamsLw(state, state.getAttrs(), true /* hasStatusBarPermission */);
236            assertEquals(WindowManagerGlobal.ADD_OKAY, prepareAddWindowLw(state, state.getAttrs()));
237        }
238
239        public static TestablePhoneWindowManager create(Context context) {
240            TestablePhoneWindowManager[] policy = new TestablePhoneWindowManager[1];
241            InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
242                policy[0] = new TestablePhoneWindowManager();
243                policy[0].mContext = context;
244                policy[0].mKeyguardDelegate = mock(KeyguardServiceDelegate.class);
245                policy[0].mAccessibilityManager = new AccessibilityManager(context,
246                        mock(IAccessibilityManager.class), UserHandle.USER_CURRENT);
247                policy[0].mSystemGestures = mock(SystemGesturesPointerEventListener.class);
248                policy[0].mNavigationBarCanMove = true;
249                policy[0].mPortraitRotation = ROTATION_0;
250                policy[0].mLandscapeRotation = ROTATION_90;
251                policy[0].mUpsideDownRotation = ROTATION_180;
252                policy[0].mSeascapeRotation = ROTATION_270;
253                policy[0].onConfigurationChanged();
254            });
255            return policy[0];
256        }
257    }
258}
259