blob: 6b8be2d2f12a2f34c65efc05c71aaa45390d4522 [file] [log] [blame]
/*
* Copyright (C) 2020 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import android.app.ActivityTaskManager;
import android.content.ComponentName;
import android.os.SystemClock;
import android.platform.test.annotations.Postsubmit;
import android.provider.Settings;
import android.server.wm.annotation.Group3;
import android.server.wm.app.Components;
import android.server.wm.settings.SettingsSession;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.view.KeyEvent;
import com.android.compatibility.common.util.SystemUtil;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
/**
* Test scenarios where crash dialogs should not be shown if they are not supported.
*
* <p>Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:UnsupportedErrorDialogTests
*/
@Group3
@Postsubmit
public class UnsupportedErrorDialogTests extends ActivityManagerTestBase {
private final UiDevice mUiDevice = UiDevice.getInstance(mInstrumentation);
@Override
@Before
public void setUp() throws Exception {
// These tests are for cases when error dialogs are not supported
Assume.assumeFalse(ActivityTaskManager.currentUiModeSupportsErrorDialogs(mContext));
super.setUp();
resetAppErrors();
}
/** Make sure the developer options apply correctly leading to the dialog being shown. */
@Test
public void testDevSettingOverride() {
try (SettingsSession<Integer> devDialogShow =
secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
SettingsSession<Integer> showOnFirstCrash =
globalIntSession(Settings.Global.SHOW_FIRST_CRASH_DIALOG);
SettingsSession<Integer> showOnFirstCrashDev =
secureIntSession(Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION)) {
// set developer setting to show dialogs anyway
devDialogShow.set(1);
// enable only the regular option for showing the crash dialog after the first crash
showOnFirstCrash.set(1);
showOnFirstCrashDev.set(0);
launchActivityNoWait(Components.CRASHING_ACTIVITY);
findCrashDialogAndCloseApp();
ensureActivityNotFocused(Components.CRASHING_ACTIVITY);
resetAppErrors();
// enable only the dev option for showing the crash dialog after the first crash
showOnFirstCrash.set(0);
showOnFirstCrashDev.set(1);
launchActivityNoWait(Components.CRASHING_ACTIVITY);
findCrashDialogAndCloseApp();
ensureActivityNotFocused(Components.CRASHING_ACTIVITY);
}
}
/**
* Make sure the dialog appears if the dev option is set even if the user specifically
* set to suppress it.
*/
@Test
public void testDevSettingPrecedence() {
try (SettingsSession<Integer> devDialogShow =
secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
SettingsSession<Integer> userDialogHide =
globalIntSession(Settings.Global.HIDE_ERROR_DIALOGS);
SettingsSession<Integer> showOnFirstCrash =
globalIntSession(Settings.Global.SHOW_FIRST_CRASH_DIALOG)
) {
devDialogShow.set(1);
showOnFirstCrash.set(1);
userDialogHide.set(1);
launchActivityNoWait(Components.CRASHING_ACTIVITY);
findCrashDialogAndCloseApp();
ensureActivityNotFocused(Components.CRASHING_ACTIVITY);
}
}
/** Make sure the AppError dialog is not shown even if would have after the initial crash. */
@Test
public void testFirstCrashDialogNotShown() {
try (SettingsSession<Integer> devDialogShow =
secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
SettingsSession<Integer> showOnFirstCrash =
globalIntSession(Settings.Global.SHOW_FIRST_CRASH_DIALOG)) {
devDialogShow.set(0);
// enable showing crash dialog after first crash
showOnFirstCrash.set(1);
launchActivityNoWait(Components.CRASHING_ACTIVITY);
ensureNoCrashDialog(Components.CRASHING_ACTIVITY);
ensureActivityNotFocused(Components.CRASHING_ACTIVITY);
}
}
/** Ensure the AppError dialog is not shown even after multiple crashes. */
@Test
public void testRepeatedCrashDialogNotShown() {
try (SettingsSession<Integer> devDialogShow =
secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
SettingsSession<Integer> showOnFirstCrash =
globalIntSession("show_first_crash_dialog");
SettingsSession<Integer> showOnFirstCrashDev =
secureIntSession("show_first_crash_dialog_dev_option")) {
// disable all overrides
devDialogShow.set(0);
showOnFirstCrash.set(0);
showOnFirstCrashDev.set(0);
// repeatedly crash the app without resetting AppErrors
for (int i = 0; i < 5; i++) {
launchActivityNoWait(Components.CRASHING_ACTIVITY);
ensureNoCrashDialog(Components.CRASHING_ACTIVITY);
}
ensureActivityNotFocused(Components.CRASHING_ACTIVITY);
}
}
/** Ensure the ANR dialog is also not shown. */
@Test
public void testAnrIsNotShown() {
// leave the settings at their defaults
// launch non responsive app
executeShellCommand(getAmStartCmd(Components.UNRESPONSIVE_ACTIVITY) + " --ei "
+ Components.UnresponsiveActivity.EXTRA_ON_CREATE_DELAY_MS + " 30000");
// wait for app to be focused
mWmState.waitAndAssertAppFocus(Components.UNRESPONSIVE_ACTIVITY.getPackageName(),
2_000 /* waitTime */);
// queue up enough key events to trigger an ANR
for (int i = 0; i < 20; i++) {
injectKey(KeyEvent.KEYCODE_TAB, false /* longPress */, false /* sync */);
SystemClock.sleep(500);
mWmState.computeState();
if (!mWmState.isActivityVisible(Components.UNRESPONSIVE_ACTIVITY)) {
break;
}
}
ensureNoCrashDialog(Components.UNRESPONSIVE_ACTIVITY);
ensureActivityNotFocused(Components.UNRESPONSIVE_ACTIVITY);
}
private void findCrashDialogAndCloseApp() {
UiObject2 closeAppButton = findCloseButton();
assertNotNull("Could not find crash dialog!", closeAppButton);
closeAppButton.click();
}
private void ensureNoCrashDialog(ComponentName activity) {
UiObject2 closeButton = findCloseButton();
if (closeButton != null) {
closeButton.click();
fail("An unexpected crash dialog appeared!");
}
final int numWindows = mWmState.getWindowsByPackageName(activity.getPackageName()).size();
assertEquals(0, numWindows);
}
private void ensureActivityNotFocused(ComponentName activity) {
mWmState.computeState();
mWmState.assertNotFocusedActivity("The activity should not be focused", activity);
}
/** Attempt to find the close button of a crash or ANR dialog in at most 2 seconds. */
private UiObject2 findCloseButton() {
return mUiDevice.wait(
Until.findObject(By.res("android:id/aerr_close")),
2_000);
}
private void resetAppErrors() {
SystemUtil.runWithShellPermissionIdentity(mAm::resetAppErrors,
android.Manifest.permission.RESET_APP_ERRORS);
}
private SettingsSession<Integer> globalIntSession(String settingName) {
return new SettingsSession<>(
Settings.Global.getUriFor(settingName),
Settings.Global::getInt, Settings.Global::putInt);
}
private SettingsSession<Integer> secureIntSession(String settingName) {
return new SettingsSession<>(
Settings.Secure.getUriFor(settingName),
Settings.Secure::getInt, Settings.Secure::putInt);
}
}