blob: b0955af8922acaea143975fe291da6421021c1ae [file] [log] [blame]
/*
* 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 android.server.wm;
import android.app.Instrumentation;
import android.content.ContentResolver;
import android.graphics.Rect;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.FlakyTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.provider.Settings.Global.WINDOW_ANIMATION_SCALE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.compatibility.common.util.SystemUtil;
/**
* Test whether WindowManager performs the correct layout after we make some changes to it.
*
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:LayoutTests
*/
@Presubmit
@FlakyTest(detail = "Can be promoted to pre-submit once confirmed stable.")
@RunWith(AndroidJUnit4.class)
public class LayoutTests {
private static final long TIMEOUT_LAYOUT = 200; // milliseconds
private static final long TIMEOUT_RECEIVE_KEY = 100;
private static final long TIMEOUT_SYSTEM_UI_VISIBILITY_CHANGE = 1000;
private static final int SYSTEM_UI_FLAG_HIDE_ALL = View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
private Instrumentation mInstrumentation;
private LayoutTestsActivity mActivity;
private ContentResolver mResolver;
private float mWindowAnimationScale;
private boolean mSystemUiFlagsGotCleared = false;
private boolean mChildWindowHasFocus = false;
private boolean mChildWindowGotKeyEvent = false;
@Rule
public final ActivityTestRule<LayoutTestsActivity> mActivityRule =
new ActivityTestRule<>(LayoutTestsActivity.class);
@Before
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mResolver = mInstrumentation.getContext().getContentResolver();
SystemUtil.runWithShellPermissionIdentity(() -> {
// The layout will be performed at the end of the animation of hiding status/navigation
// bar, which will recover the possible issue, so we disable the animation during the
// test.
mWindowAnimationScale = Settings.Global.getFloat(mResolver, WINDOW_ANIMATION_SCALE, 1f);
Settings.Global.putFloat(mResolver, WINDOW_ANIMATION_SCALE, 0);
});
}
@After
public void tearDown() {
SystemUtil.runWithShellPermissionIdentity(() -> {
// Restore the animation we disabled previously.
Settings.Global.putFloat(mResolver, WINDOW_ANIMATION_SCALE, mWindowAnimationScale);
});
}
@Test
public void testLayoutAfterRemovingFocus() {
mActivity = mActivityRule.getActivity();
assertNotNull(mActivity);
// Get the visible frame of the main activity before adding any window.
final Rect visibleFrame = new Rect();
mActivity.getWindowVisibleDisplayFrame(visibleFrame);
doTestLayoutAfterRemovingFocus(visibleFrame, mActivity::addWindowHidingStatusBar);
doTestLayoutAfterRemovingFocus(visibleFrame, mActivity::addWindowHidingNavigationBar);
doTestLayoutAfterRemovingFocus(visibleFrame, mActivity::addWindowHidingBothSystemBars);
}
private void doTestLayoutAfterRemovingFocus(Rect visibleFrameBeforeAddingWindow,
Runnable toAddWindow) {
// Add a window which can affect the global layout.
mInstrumentation.runOnMainSync(toAddWindow);
// Wait a bit for the global layout finish triggered by adding window.
SystemClock.sleep(TIMEOUT_LAYOUT);
// Remove the window we added previously.
mInstrumentation.runOnMainSync(mActivity::removeWindow);
mInstrumentation.waitForIdleSync();
// Get the visible frame of the main activity after removing the window we added.
final Rect visibleFrameAfterRemovingWindow = new Rect();
mActivity.getWindowVisibleDisplayFrame(visibleFrameAfterRemovingWindow);
// Test whether the visible frame after removing window is the same as one before adding
// window. If not, it shows that the layout after removing window has a problem.
assertEquals(visibleFrameBeforeAddingWindow, visibleFrameAfterRemovingWindow);
}
private void stopWaiting() {
synchronized (this) {
notify();
}
}
@Test
public void testAddingImmersiveWindow() throws InterruptedException {
mActivity = mActivityRule.getActivity();
assertNotNull(mActivity);
mInstrumentation.runOnMainSync(this::addImmersiveWindow);
synchronized (this) {
wait(TIMEOUT_SYSTEM_UI_VISIBILITY_CHANGE);
}
assertFalse("System UI flags must not be cleared.", mSystemUiFlagsGotCleared);
}
private void addImmersiveWindow() {
final View view = new View(mActivity);
view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | SYSTEM_UI_FLAG_HIDE_ALL);
view.setOnSystemUiVisibilityChangeListener(
visibility -> {
if ((visibility & SYSTEM_UI_FLAG_HIDE_ALL) != SYSTEM_UI_FLAG_HIDE_ALL) {
mSystemUiFlagsGotCleared = true;
// Early break because things go wrong already
stopWaiting();
}
});
mActivity.addWindow(view, new LayoutParams(TYPE_APPLICATION_PANEL));
}
@Test
public void testChangingFocusableFlag() throws InterruptedException {
mActivity = mActivityRule.getActivity();
assertNotNull(mActivity);
// Add a not-focusable window.
mInstrumentation.runOnMainSync(this::addNotFocusableWindow);
mInstrumentation.waitForIdleSync();
// Make the window focusable.
mInstrumentation.runOnMainSync(this::makeWindowFocusable);
synchronized (this) {
wait(TIMEOUT_LAYOUT);
}
// The window must have focus.
assertTrue("Child window must have focus.", mChildWindowHasFocus);
// Send a key event to the system.
mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_0);
synchronized (this) {
wait(TIMEOUT_RECEIVE_KEY);
}
// The window must get the key event.
assertTrue("Child window must get key event.", mChildWindowGotKeyEvent);
}
private void addNotFocusableWindow() {
final View view = new View(mActivity) {
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
mChildWindowHasFocus = hasWindowFocus;
stopWaiting();
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
mChildWindowGotKeyEvent = true;
stopWaiting();
return super.onKeyDown(keyCode, event);
}
};
mActivity.addWindow(view, new LayoutParams(TYPE_APPLICATION_PANEL, FLAG_NOT_FOCUSABLE));
}
private void makeWindowFocusable() {
mActivity.updateLayoutForAddedWindow(new LayoutParams(TYPE_APPLICATION_PANEL));
}
}