blob: 0c8f01d86ee4ef479a66b6f4ab4904dfc93fc447 [file] [log] [blame]
/*
* Copyright (C) 2019 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.activity;
import static android.server.wm.ShellCommandHelper.executeShellCommand;
import static android.server.wm.app.Components.HOST_ACTIVITY;
import static android.server.wm.app.Components.UNRESPONSIVE_ACTIVITY;
import static android.server.wm.app.Components.UnresponsiveActivity;
import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_CREATE_DELAY_MS;
import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_KEYDOWN_DELAY_MS;
import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_MOTIONEVENT_DELAY_MS;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.app.ActivityManager;
import android.app.ApplicationExitInfo;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.server.wm.ActivityManagerTestBase;
import android.server.wm.BuildUtils;
import android.server.wm.WindowManagerState;
import android.server.wm.app.Components.RenderService;
import android.server.wm.settings.SettingsSession;
import android.util.EventLog;
import android.util.Log;
import android.view.KeyEvent;
import androidx.test.core.app.ActivityScenario;
import androidx.test.filters.FlakyTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
import com.android.compatibility.common.util.PollingCheck;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* Test scenarios that lead to "Application Not
* Responding" (ANR) dialog being shown.
*
* <p>Build/Install/Run:
* atest CtsWindowManagerDeviceActivity:AnrTests
*/
@Presubmit
@android.server.wm.annotation.Group3
public class AnrTests extends ActivityManagerTestBase {
private static final String TAG = "AnrTests";
private LogSeparator mLogSeparator;
private SettingsSession<Integer> mHideDialogSetting;
@Before
public void setup() throws Exception {
super.setUp();
assumeTrue(mAtm.currentUiModeSupportsErrorDialogs(mContext));
mLogSeparator = separateLogs(); // add a new separator for logs
mHideDialogSetting = new SettingsSession<>(
Settings.Global.getUriFor(Settings.Global.HIDE_ERROR_DIALOGS),
Settings.Global::getInt, Settings.Global::putInt);
mHideDialogSetting.set(0);
}
@After
public void teardown() {
if (mHideDialogSetting != null) mHideDialogSetting.close();
stopTestPackage(UNRESPONSIVE_ACTIVITY.getPackageName());
stopTestPackage(HOST_ACTIVITY.getPackageName());
}
@Test
public void slowOnCreateWithKeyEventTriggersAnr() {
startUnresponsiveActivity(EXTRA_ON_CREATE_DELAY_MS, false /* waitForCompletion */,
UNRESPONSIVE_ACTIVITY);
// wait for app to be focused
mWmState.waitAndAssertAppFocus(UNRESPONSIVE_ACTIVITY.getPackageName(),
2000 /* waitTime_ms */);
// wait for input manager to get the new focus app. This sleep can be removed once we start
// listening to input about the focused app.
SystemClock.sleep(500);
injectKey(KeyEvent.KEYCODE_A, false /* longpress */, false /* sync */);
clickCloseAppOnAnrDialog(UNRESPONSIVE_ACTIVITY.getPackageName());
assertEventLogsContainsAnr(UnresponsiveActivity.PROCESS_NAME);
}
@Test
public void slowOnKeyEventHandleTriggersAnr() {
startUnresponsiveActivity(EXTRA_ON_KEYDOWN_DELAY_MS, true /* waitForCompletion */,
UNRESPONSIVE_ACTIVITY);
injectKey(KeyEvent.KEYCODE_A, false /* longpress */, false /* sync */);
clickCloseAppOnAnrDialog(UNRESPONSIVE_ACTIVITY.getPackageName());
assertEventLogsContainsAnr(UnresponsiveActivity.PROCESS_NAME);
}
@Test
public void slowOnTouchEventHandleTriggersAnr() {
startUnresponsiveActivity(EXTRA_ON_MOTIONEVENT_DELAY_MS, true /* waitForCompletion */,
UNRESPONSIVE_ACTIVITY);
mWmState.computeState();
// Tap on the UnresponsiveActivity
final WindowManagerState.Task unresponsiveActivityTask =
mWmState.getTaskByActivity(UNRESPONSIVE_ACTIVITY);
mTouchHelper.tapOnTaskCenterAsync(unresponsiveActivityTask);
clickCloseAppOnAnrDialog(UNRESPONSIVE_ACTIVITY.getPackageName());
assertEventLogsContainsAnr(UnresponsiveActivity.PROCESS_NAME);
}
/**
* Verify embedded windows can trigger ANR and the verify embedded app is blamed.
*/
@Test
@FlakyTest(bugId = 296860841)
public void embeddedWindowTriggersAnr() {
try (ActivityScenario<HostActivity> scenario =
ActivityScenario.launch(HostActivity.class)) {
CountDownLatch[] latch = new CountDownLatch[1];
scenario.onActivity(activity -> latch[0] = activity.mEmbeddedViewAttachedLatch);
latch[0].await();
mWmState.computeState();
final WindowManagerState.Task hostActivityTask =
mWmState.getTaskByActivity(new ComponentName("android.server.wm.cts",
"android.server.wm.activity.HostActivity"));
mTouchHelper.tapOnTaskCenterAsync(hostActivityTask);
clickCloseAppOnAnrDialog("android.server.wm.app");
} catch (InterruptedException ignored) {
}
assertEventLogsContainsAnr(RenderService.PROCESS_NAME);
}
private void assertEventLogsContainsAnr(String processName) {
final List<EventLog.Event> events = getEventLogsForComponents(mLogSeparator,
android.util.EventLog.getTagCode("am_anr"));
for (EventLog.Event event : events) {
Object[] arr = (Object[]) event.getData();
final String name = (String) arr[2];
if (name.equals(processName)) {
return;
}
}
fail("Could not find anr kill event for " + processName);
}
private void clickCloseAppOnAnrDialog(String packageName) {
// Find anr dialog and kill app
final long timestamp = System.currentTimeMillis();
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
UiObject2 closeAppButton = uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")),
20000 * BuildUtils.HW_TIMEOUT_MULTIPLIER);
if (closeAppButton == null) {
fail("Could not find anr dialog");
return;
}
closeAppButton.click();
Log.d(TAG, "found permission dialog after searching all windows, clicked");
/*
We must wait for the app to be fully closed before exiting this test. This is because
another test may again invoke 'am start' for the same activity.
If the 1st process that got ANRd isn't killed by the time second 'am start' runs,
the killing logic will apply to the newly launched 'am start' instance, and the second
test will fail because the unresponsive activity will never be launched.
*/
waitForNewExitReasonAfter(timestamp, packageName);
}
private void startUnresponsiveActivity(String delayTypeExtra, boolean waitForCompletion,
ComponentName activity) {
String flags = waitForCompletion ? " -W -n " : " -n ";
String startCmd = "am start" + flags + activity.flattenToString() +
" --ei " + delayTypeExtra + " 60000";
executeShellCommand(startCmd);
}
private List<ApplicationExitInfo> getExitReasons(String packageName) {
final List<ApplicationExitInfo>[] infos = new List[]{new ArrayList<>()};
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
instrumentation.runOnMainSync(() -> {
ActivityManager am = (ActivityManager) instrumentation.getContext()
.getSystemService(Context.ACTIVITY_SERVICE);
infos[0] = am.getHistoricalProcessExitReasons(packageName, /*all pids*/0, /* no max*/0);
});
return infos[0];
}
private void waitForNewExitReasonAfter(long timestamp, String packageName) {
PollingCheck.waitFor(() -> {
List<ApplicationExitInfo> reasons = getExitReasons(packageName);
return !reasons.isEmpty() && reasons.get(0).getTimestamp() >= timestamp;
});
List<ApplicationExitInfo> reasons = getExitReasons(packageName);
assertTrue(reasons.get(0).getTimestamp() > timestamp);
assertEquals(ApplicationExitInfo.REASON_ANR, reasons.get(0).getReason());
}
}