Merge tag 'android-11.0.0_r38' of https://android.googlesource.com/platform//platform_testing into HEAD
Android 11.0.0 Release 38 (RQ3A.210605.005)
Change-Id: Icf12ab16e91e6002211120b735c3364132a9aee3
diff --git a/libraries/annotations/src/android/platform/test/annotations/RootPermissionTest.java b/libraries/annotations/src/android/platform/test/annotations/RootPermissionTest.java
index 25496ef..f520ef6 100644
--- a/libraries/annotations/src/android/platform/test/annotations/RootPermissionTest.java
+++ b/libraries/annotations/src/android/platform/test/annotations/RootPermissionTest.java
@@ -34,4 +34,10 @@
// TODO : Remove the default value. Need one in the interim whilst we undertake
// the effort to annotate the existing tests with a corresponding patch-level.
String minPatchLevel() default "";
+
+ // Denotes the CVE ID(s), comma-separated, to which this test applies.
+ String cve() default "";
+
+ // Denotes the scope (platform/kernel/vendor) to which this test applies.
+ String scope() default "";
}
diff --git a/libraries/annotations/src/android/platform/test/annotations/SecurityTest.java b/libraries/annotations/src/android/platform/test/annotations/SecurityTest.java
index 56ae4b2..1154c6f 100644
--- a/libraries/annotations/src/android/platform/test/annotations/SecurityTest.java
+++ b/libraries/annotations/src/android/platform/test/annotations/SecurityTest.java
@@ -33,4 +33,10 @@
// TODO : Remove the default value. Need one in the interim whilst we undertake
// the effort to annotate the existing tests with a corresponding patch-level.
String minPatchLevel() default "";
+
+ // Denotes the CVE ID(s), comma-separated, to which this test applies.
+ String cve() default "";
+
+ // Denotes the scope (platform/kernel/vendor) to which this test applies.
+ String scope() default "";
}
diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoAppInfoSettingsHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoAppInfoSettingsHelper.java
index d08d310..cc6532b 100644
--- a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoAppInfoSettingsHelper.java
+++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoAppInfoSettingsHelper.java
@@ -83,4 +83,22 @@
* Get the current enabled permission summary in String format for an application
*/
String getCurrentPermissions();
+
+ /**
+ * Setup expectation: None
+ *
+ * <p>This method is to check if an application has been disabled.
+ *
+ * @param packageName - package of the application to be checked.
+ */
+ boolean isApplicationDisabled(String packageName);
+
+ /**
+ * Setup expectation: None
+ *
+ * <p>This method is to check open an application.
+ *
+ * @param appName - Name of the app to be opened.
+ */
+ void openApp(String packageName);
}
diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoDialHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoDialHelper.java
index 77ec8a3..37cd8d8 100644
--- a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoDialHelper.java
+++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoDialHelper.java
@@ -230,4 +230,17 @@
* <p>This method is used to check if phone is paired.
*/
boolean isPhonePaired();
+ /**
+ * Setup expectations: The app is open.
+ *
+ * <p>This method is used to open contact list
+ */
+ void openContacts();
+
+ /**
+ * This method is used to open the Phone App
+ *
+ * @param No parameters.
+ */
+ void OpenPhoneApp();
}
diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoGooglePlayHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoGooglePlayHelper.java
index d3330db..331eec3 100644
--- a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoGooglePlayHelper.java
+++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoGooglePlayHelper.java
@@ -19,6 +19,13 @@
public interface IAutoGooglePlayHelper extends IAppHelper, Scrollable {
/**
+ * Setup expectations: Launch Google Play Store app.
+ *
+ * <p>This method is used to Open Google Play Store app.
+ */
+ void openGooglePlayStore();
+
+ /**
* Setup expectations: Google Play app is open.
*
* <p>This method is used to search an app and click it in Google Play.
@@ -60,4 +67,11 @@
*/
@Deprecated
void openApp();
+
+ /**
+ * Setup expectations: None.
+ *
+ * <p>This method is used to check if the given application package is installed.
+ */
+ boolean checkIfApplicationExists(String packageName);
}
diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterHelper.java
index 92180a6..81d53d8 100644
--- a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterHelper.java
+++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterHelper.java
@@ -110,4 +110,11 @@
* @param - menuOptions used to pass multiple level of menu options in one go.
*/
void selectMediaTrack(String... menuOptions);
+
+ /**
+ * Setup expectations: Launch Media App.
+ *
+ * @param - String packagename - Android media package to be opened.
+ */
+ void openMediaApp(String packagename);
}
diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java
index 569c5a3..960921f 100644
--- a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java
+++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java
@@ -128,4 +128,11 @@
* else returns false
*/
boolean isPlaying();
+
+ /**
+ * Setup expectations: Launch Media App.
+ *
+ * @param - String packagename - Android media package to be opened.
+ */
+ void openMediaApp(String packagename);
}
diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoSettingHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoSettingHelper.java
index 2c1e527..73ffb18 100644
--- a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoSettingHelper.java
+++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoSettingHelper.java
@@ -237,4 +237,20 @@
* @param name of the setting menu.
*/
boolean isSettingMenuEnabled(String menu);
+
+ /**
+ * Setup expectations: Setting is open.
+ *
+ * <p>Get the current page title text.
+ */
+ String getPageTitleText();
+
+ /**
+ * Setup expectations: Setting is open.
+ *
+ * <p>Find the setting menu and perform a click action.
+ *
+ * @param name of the setting menu.
+ */
+ void findSettingMenuAndClick(String setting);
}
diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/utility/IAutoNavigationBarHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/utility/IAutoNavigationBarHelper.java
new file mode 100644
index 0000000..227d29f
--- /dev/null
+++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/utility/IAutoNavigationBarHelper.java
@@ -0,0 +1,73 @@
+/*
+ * 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.platform.helpers;
+
+public interface IAutoNavigationBarHelper extends IAppHelper {
+ /**
+ * Setup expectation: None.
+ *
+ * <p>This method is to open Home page using Facet.
+ */
+ void openHomeFacet();
+ /**
+ * Setup expectation: None.
+ *
+ * <p>This method is to open Maps application using Facet.
+ */
+ void openMapsFacet();
+ /**
+ * Setup expectation: None.
+ *
+ * <p>This method is to open Media application using Facet.
+ */
+ void openMediaFacet();
+ /**
+ * Setup expectation: Media Application is open.
+ *
+ * <p>This method is to open given Media application.
+ */
+ void openMediaFacet(String appName);
+ /**
+ * Setup expectation: None.
+ *
+ * <p>This method is to open Dial application using Facet.
+ */
+ void openDialFacet();
+ /**
+ * Setup expectation: None.
+ *
+ * <p>This method is to open App Grid using Facet.
+ */
+ void openAppGridFacet();
+ /**
+ * Setup expectation: None.
+ *
+ * <p>This method is to open Notifications using Facet.
+ */
+ void openNotificationsFacet();
+ /**
+ * Setup expectation: None.
+ *
+ * <p>This method is to open Assistant application using Facet.
+ */
+ void openAssistantFacet();
+ /**
+ * Setup expectation: None.
+ *
+ * <p>This method is to open Quick Settings.
+ */
+ void openQuickSettings();
+}
diff --git a/libraries/app-helpers/interfaces/common/src/android/platform/helpers/INewsHelper.java b/libraries/app-helpers/interfaces/common/src/android/platform/helpers/INewsHelper.java
index a06b058..34e6d45 100644
--- a/libraries/app-helpers/interfaces/common/src/android/platform/helpers/INewsHelper.java
+++ b/libraries/app-helpers/interfaces/common/src/android/platform/helpers/INewsHelper.java
@@ -16,7 +16,9 @@
package android.platform.helpers;
+import android.graphics.Rect;
import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
public interface INewsHelper extends IAppHelper {
/**
@@ -25,4 +27,22 @@
* <p>Scroll the page by specified direction.
*/
public void scrollPage(Direction direction);
+
+ /**
+ * Setup expectation: On the home screen.
+ *
+ * <p>Get the UiObject2 of News scroll container.
+ */
+ public default UiObject2 getNewsScrollContainer() {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
+ /**
+ * Setup expectation: On the home screen.
+ *
+ * <p>Scroll the page to view the news.
+ */
+ public default void scrollPage(Rect bounds, Direction dir, float percent) {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
}
diff --git a/libraries/app-helpers/interfaces/common/src/android/platform/helpers/ISettingsIntelligenceHelper.java b/libraries/app-helpers/interfaces/common/src/android/platform/helpers/ISettingsIntelligenceHelper.java
index 6f9e551..bb26e95 100644
--- a/libraries/app-helpers/interfaces/common/src/android/platform/helpers/ISettingsIntelligenceHelper.java
+++ b/libraries/app-helpers/interfaces/common/src/android/platform/helpers/ISettingsIntelligenceHelper.java
@@ -18,6 +18,21 @@
public interface ISettingsIntelligenceHelper extends IAppHelper {
+ public static final String PAGE_ACTION_HOME = "";
+ public static final String PAGE_ACTION_APPLICATION = "android.settings.APPLICATION_SETTINGS";
+ public static final String PAGE_ACTION_BATTERY = "android.intent.action.POWER_USAGE_SUMMARY";
+ public static final String PAGE_ACTION_BLUETOOTH = "android.settings.BLUETOOTH_SETTINGS";
+ public static final String PAGE_ACTION_LOCATION = "android.settings.LOCATION_SOURCE_SETTINGS";
+ public static final String PAGE_ACTION_STORAGE = "android.settings.INTERNAL_STORAGE_SETTINGS";
+ public static final String PAGE_ACTION_WIFI = "android.settings.WIFI_SETTINGS";
+
+ /**
+ * Sets the action representing the Settings page to open when open() is called.
+ *
+ * @param pageAction One of the PAGE_ACTION* constants.
+ */
+ void setPageAction(String pageAction);
+
/**
* Setup expectations: Settings search page is open
*
@@ -31,4 +46,4 @@
* <p>This method opens search page.
*/
void openSearch();
-}
+}
\ No newline at end of file
diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/GcaEventLogCollector.java b/libraries/device-collectors/src/main/java/android/device/collectors/GcaEventLogCollector.java
new file mode 100644
index 0000000..c75b660
--- /dev/null
+++ b/libraries/device-collectors/src/main/java/android/device/collectors/GcaEventLogCollector.java
@@ -0,0 +1,236 @@
+/*
+ * 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.device.collectors;
+
+import android.device.collectors.annotations.OptionClass;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+
+/**
+ * A {@link GcaEventLogCollector} that captures Google Camera App (GCA) on-device event log protos
+ * and save them to external storage at run_listeners/camera_events directory. This collector
+ * disables selinux at the start of the test run and recover at the end. So if your tests require
+ * selinux enforcing, please do not use this collector.
+ */
+@OptionClass(alias = "gca-proto-log-collector")
+public class GcaEventLogCollector extends BaseMetricListener {
+
+ private static final String TAG = GcaEventLogCollector.class.getSimpleName();
+ private static final String DEST_DIR = "run_listeners/camera_events";
+ private static final String CAMERA_EVENT_LOG_PATTERN =
+ "/data/user/0/%s/files/camera_events/session.pb";
+
+ @VisibleForTesting
+ static final String COLLECT_TEST_FAILURE_CAMERA_LOGS = "collect_test_failure_camera_logs";
+
+ @VisibleForTesting
+ static final String COLLECT_CAMERA_LOGS_PER_RUN = "collect_camera_logs_per_run";
+
+ private static final String GOOGLE_CAMERA_APP_PACKAGE = "google_camera_app_package";
+
+ enum SeLinuxEnforceProperty {
+ ENFORCING("1"),
+ PERMISSIVE("0");
+
+ private String property;
+
+ public String getProperty() {
+ return property;
+ }
+
+ private SeLinuxEnforceProperty(String property) {
+ this.property = property;
+ }
+ }
+
+ private String mGcaPkg = "com.google.android.GoogleCamera";
+ private boolean mIsCollectPerRun = false;
+ private boolean mCollectMetricsForFailedTest = false;
+ private boolean mIsTestFailed;
+ private File mDestDir;
+ private String mEventLogPath;
+ private SeLinuxEnforceProperty mOrigSeLinuxEnforceProp;
+ private String mOrigCameraEventProp;
+
+ // Map to keep track of test iterations for multiple test iterations.
+ private HashMap<Description, Integer> mTestIterations = new HashMap<>();
+
+ public GcaEventLogCollector() {
+ super();
+ }
+
+ @VisibleForTesting
+ GcaEventLogCollector(Bundle args) {
+ super(args);
+ }
+
+ @Override
+ public void onTestRunStart(DataRecord runData, Description description) {
+ setupAdditionalArgs();
+ mDestDir = createAndEmptyDirectory(DEST_DIR);
+ mEventLogPath = String.format(CAMERA_EVENT_LOG_PATTERN, mGcaPkg);
+ cleanUpEventLog();
+ mOrigSeLinuxEnforceProp = getSeLinuxEnforceProperty();
+ mOrigCameraEventProp = getCameraEventLogProperty();
+ setSeLinuxEnforceProperty(SeLinuxEnforceProperty.PERMISSIVE);
+ setCameraEventLogProperty("1");
+ killGoogleCameraApp();
+ }
+
+ @Override
+ public void onTestRunEnd(DataRecord runData, Result result) {
+ try {
+ if (mIsCollectPerRun) {
+ collectEventLog(runData, null);
+ }
+ setCameraEventLogProperty(mOrigCameraEventProp);
+ killGoogleCameraApp();
+ } finally {
+ setSeLinuxEnforceProperty(mOrigSeLinuxEnforceProp);
+ }
+ }
+
+ @Override
+ public void onTestStart(DataRecord runData, Description description) {
+ mIsTestFailed = false;
+ if (!mIsCollectPerRun) {
+ killGoogleCameraApp();
+ cleanUpEventLog();
+ }
+ // Keep track of test iterations.
+ mTestIterations.computeIfPresent(description, (desc, iteration) -> iteration + 1);
+ mTestIterations.computeIfAbsent(description, desc -> 1);
+ }
+
+ @Override
+ public void onTestEnd(DataRecord testData, Description description) {
+ if (mIsCollectPerRun) {
+ return;
+ }
+ if (!mCollectMetricsForFailedTest && mIsTestFailed) {
+ return;
+ }
+ collectEventLog(testData, description);
+ }
+
+ @Override
+ public void onTestFail(DataRecord runData, Description description, Failure failure) {
+ mIsTestFailed = true;
+ }
+
+ private void setupAdditionalArgs() {
+ Bundle args = getArgsBundle();
+ if (!Strings.isNullOrEmpty(args.getString(GOOGLE_CAMERA_APP_PACKAGE))) {
+ mGcaPkg = args.getString(GOOGLE_CAMERA_APP_PACKAGE);
+ }
+ mIsCollectPerRun =
+ Boolean.parseBoolean(args.getString(COLLECT_CAMERA_LOGS_PER_RUN, "false"));
+ mCollectMetricsForFailedTest =
+ Boolean.parseBoolean(args.getString(COLLECT_TEST_FAILURE_CAMERA_LOGS, "false"));
+ }
+
+ private void collectEventLog(DataRecord testData, Description description) {
+ logListResult(mEventLogPath);
+ String fileName = buildCollectedEventLogFileName(description);
+ Log.d(TAG, String.format("Destination event log file name: %s", fileName));
+ copyEventLogToSharedStorage(fileName);
+ testData.addFileMetric(fileName, new File(mDestDir, fileName));
+ }
+
+ @VisibleForTesting
+ protected void copyEventLogToSharedStorage(String fileName) {
+ String cmd =
+ String.format("cp %s %s/%s", mEventLogPath, mDestDir.getAbsolutePath(), fileName);
+ Log.d(TAG, "Copy command: " + cmd);
+ executeCommandBlocking(cmd);
+ logListResult(mDestDir.getAbsolutePath());
+ }
+
+ private void cleanUpEventLog() {
+ executeCommandBlocking("rm " + mEventLogPath);
+ logListResult(mEventLogPath);
+ }
+
+ private String getCameraEventLogProperty() {
+ String res =
+ new String(
+ executeCommandBlocking("getprop camera.use_local_logger"),
+ StandardCharsets.UTF_8)
+ .trim();
+ return res.isEmpty() ? "0" : res;
+ }
+
+ private void setCameraEventLogProperty(String value) {
+ if (!getCameraEventLogProperty().equals(value)) {
+ Log.d(TAG, "Setting property camera.use_local_logger to " + value);
+ executeCommandBlocking("setprop camera.use_local_logger " + value);
+ }
+ }
+
+ private void killGoogleCameraApp() {
+ executeCommandBlocking("am force-stop " + mGcaPkg);
+ }
+
+ private SeLinuxEnforceProperty getSeLinuxEnforceProperty() {
+ String res =
+ new String(executeCommandBlocking("getenforce"), StandardCharsets.UTF_8).trim();
+ return SeLinuxEnforceProperty.valueOf(res.toUpperCase());
+ }
+
+ private void setSeLinuxEnforceProperty(SeLinuxEnforceProperty value) {
+ if (getSeLinuxEnforceProperty() != value) {
+ Log.d(TAG, "Setting SeLinux enforcing to " + value.getProperty());
+ executeCommandBlocking("setenforce " + value.getProperty());
+ }
+ }
+
+ private String buildCollectedEventLogFileName(@Nullable Description description) {
+ long timestamp = System.currentTimeMillis();
+ if (description == null) {
+ return String.format("session_run_%d.pb", timestamp);
+ }
+ int iteration = mTestIterations.get(description);
+ return String.format(
+ "session_%s_%s%s_%d.pb",
+ description.getClassName(),
+ description.getMethodName(),
+ iteration == 1 ? "" : ("_" + String.valueOf(iteration)),
+ timestamp);
+ }
+
+ private void logListResult(String targetPath) {
+ byte[] lsResult = executeCommandBlocking("ls " + targetPath);
+ Log.d(
+ TAG,
+ String.format(
+ "List path %s result: %s",
+ targetPath, new String(lsResult, StandardCharsets.UTF_8)));
+ }
+}
diff --git a/libraries/device-collectors/src/test/java/Android.bp b/libraries/device-collectors/src/test/java/Android.bp
index 025b393..244e1bd 100644
--- a/libraries/device-collectors/src/test/java/Android.bp
+++ b/libraries/device-collectors/src/test/java/Android.bp
@@ -21,6 +21,7 @@
srcs: ["android/**/*.java"],
static_libs: [
+ "androidx.test.ext.junit",
"androidx.test.runner",
"collector-device-lib",
"junit",
diff --git a/libraries/device-collectors/src/test/java/android/device/collectors/GcaEventLogCollectorTest.java b/libraries/device-collectors/src/test/java/android/device/collectors/GcaEventLogCollectorTest.java
new file mode 100644
index 0000000..b06f686
--- /dev/null
+++ b/libraries/device-collectors/src/test/java/android/device/collectors/GcaEventLogCollectorTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2021 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.device.collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+
+import android.app.Instrumentation;
+import android.device.collectors.util.SendToInstrumentation;
+import android.os.Bundle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.Failure;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class GcaEventLogCollectorTest {
+
+ private static final Description RUN_DESCRIPTION =
+ Description.createSuiteDescription("test-run");
+ private static final Description TEST_DESCRIPTION =
+ Description.createTestDescription("TestClass", "testCase1");
+ private static final Description TEST_FAILURE_DESCRIPTION =
+ Description.createTestDescription("TestClass", "testCaseFailed");
+ @Mock private Instrumentation mMockInstrumentation;
+ private File mEventLogDir;
+ private File mDestDir;
+ private GcaEventLogCollector mCollector;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mEventLogDir = new File("/sdcard", "camera_events");
+ mDestDir = new File("/sdcard", "camera_events_dest");
+ }
+
+ @After
+ public void tearDown() {
+ mCollector.recursiveDelete(mEventLogDir);
+ mCollector.recursiveDelete(mDestDir);
+ }
+
+ private GcaEventLogCollector initListener(Bundle bundle) {
+ mCollector = new GcaEventLogCollector(bundle);
+ GcaEventLogCollector listener = Mockito.spy(mCollector);
+ listener.setInstrumentation(mMockInstrumentation);
+ Mockito.doReturn(mDestDir).when(listener).createAndEmptyDirectory(Mockito.anyString());
+ Mockito.doReturn(new byte[10]).when(listener).executeCommandBlocking(Mockito.anyString());
+ Mockito.doNothing().when(listener).copyEventLogToSharedStorage(Mockito.anyString());
+ return listener;
+ }
+
+ @Test
+ public void testGcaEventLogCollectionOnTestEnd_includeFailure() throws Exception {
+ Bundle bundle = new Bundle();
+ bundle.putString(GcaEventLogCollector.COLLECT_TEST_FAILURE_CAMERA_LOGS, "true");
+ GcaEventLogCollector listener = initListener(bundle);
+
+ listener.testRunStarted(RUN_DESCRIPTION);
+ // Test case 1
+ listener.testStarted(TEST_DESCRIPTION);
+ listener.testFinished(TEST_DESCRIPTION);
+ // Test case 2 - Failed
+ listener.testStarted(TEST_FAILURE_DESCRIPTION);
+ Failure failure =
+ new Failure(TEST_FAILURE_DESCRIPTION, new RuntimeException("Test Failed"));
+ listener.testFailure(failure);
+ listener.testFinished(TEST_FAILURE_DESCRIPTION);
+ listener.testRunFinished(new Result());
+
+ Bundle resultBundle = new Bundle();
+ listener.instrumentationRunFinished(System.out, resultBundle, new Result());
+ assertEquals(0, resultBundle.size());
+
+ ArgumentCaptor<Bundle> capture = ArgumentCaptor.forClass(Bundle.class);
+ Mockito.verify(mMockInstrumentation, times(2))
+ .sendStatus(
+ Mockito.eq(SendToInstrumentation.INST_STATUS_IN_PROGRESS),
+ capture.capture());
+ List<Bundle> capturedBundle = capture.getAllValues();
+ // Include the log collected from the failed test.
+ assertEquals(2, capturedBundle.size());
+ Bundle check = capturedBundle.get(0);
+ assertEquals(1, check.size());
+ for (String key : check.keySet()) {
+ assertTrue(
+ key.contains(
+ String.format(
+ "session_%s_%s",
+ TEST_DESCRIPTION.getClassName(),
+ TEST_DESCRIPTION.getMethodName())));
+ }
+ Bundle checkFail = capturedBundle.get(1);
+ assertEquals(1, checkFail.size());
+ for (String key : checkFail.keySet()) {
+ assertTrue(
+ key.contains(
+ String.format(
+ "session_%s_%s",
+ TEST_FAILURE_DESCRIPTION.getClassName(),
+ TEST_FAILURE_DESCRIPTION.getMethodName())));
+ }
+ }
+
+ @Test
+ public void testGcaEventLogCollectOnTestEnd_skipFailure() throws Exception {
+ Bundle bundle = new Bundle();
+ bundle.putString(GcaEventLogCollector.COLLECT_TEST_FAILURE_CAMERA_LOGS, "false");
+ GcaEventLogCollector listener = initListener(bundle);
+
+ listener.testRunStarted(RUN_DESCRIPTION);
+ // 1st iteration
+ listener.testStarted(TEST_DESCRIPTION);
+ listener.testFinished(TEST_DESCRIPTION);
+ // 2nd iteration
+ listener.testStarted(TEST_DESCRIPTION);
+ listener.testFinished(TEST_DESCRIPTION);
+ // Failed test
+ listener.testStarted(TEST_FAILURE_DESCRIPTION);
+ Failure failure =
+ new Failure(TEST_FAILURE_DESCRIPTION, new RuntimeException("Test Failed"));
+ listener.testFailure(failure);
+ listener.testFinished(TEST_FAILURE_DESCRIPTION);
+ listener.testRunFinished(new Result());
+
+ Bundle resultBundle = new Bundle();
+ listener.instrumentationRunFinished(System.out, resultBundle, new Result());
+
+ assertEquals(0, resultBundle.size());
+
+ ArgumentCaptor<Bundle> capture = ArgumentCaptor.forClass(Bundle.class);
+ Mockito.verify(mMockInstrumentation, times(2))
+ .sendStatus(
+ Mockito.eq(SendToInstrumentation.INST_STATUS_IN_PROGRESS),
+ capture.capture());
+ List<Bundle> capturedBundle = capture.getAllValues();
+ // Include the log collected from the failed test.
+ assertEquals(2, capturedBundle.size());
+ Bundle check1 = capturedBundle.get(0);
+ assertEquals(1, check1.size());
+ for (String key : check1.keySet()) {
+ assertTrue(
+ key.contains(
+ String.format(
+ "session_%s_%s",
+ TEST_DESCRIPTION.getClassName(),
+ TEST_DESCRIPTION.getMethodName())));
+ }
+ Bundle check2 = capturedBundle.get(1);
+ assertEquals(1, check2.size());
+ for (String key : check2.keySet()) {
+ assertTrue(
+ key.contains(
+ String.format(
+ "session_%s_%s_2_",
+ TEST_DESCRIPTION.getClassName(),
+ TEST_DESCRIPTION.getMethodName())));
+ }
+ }
+
+ @Test
+ public void testGcaEventLogCollectionOnTestRunEnd_runEndOnly() throws Exception {
+ Bundle bundle = new Bundle();
+ bundle.putString(GcaEventLogCollector.COLLECT_CAMERA_LOGS_PER_RUN, "true");
+ GcaEventLogCollector listener = initListener(bundle);
+
+ listener.testRunStarted(RUN_DESCRIPTION);
+ // Test case 1
+ listener.testStarted(TEST_DESCRIPTION);
+ listener.testFinished(TEST_DESCRIPTION);
+ // Test case 2
+ listener.testStarted(TEST_FAILURE_DESCRIPTION);
+ Failure failure =
+ new Failure(TEST_FAILURE_DESCRIPTION, new RuntimeException("Test Failed"));
+ listener.testFailure(failure);
+ listener.testFinished(TEST_FAILURE_DESCRIPTION);
+ listener.testRunFinished(new Result());
+
+ Bundle resultBundle = new Bundle();
+ listener.instrumentationRunFinished(System.out, resultBundle, new Result());
+
+ assertEquals(1, resultBundle.size());
+ for (String key : resultBundle.keySet()) {
+ assertTrue(key.contains("session_run_"));
+ }
+ }
+}
diff --git a/libraries/health/runners/microbenchmark/src/android/platform/test/microbenchmark/Microbenchmark.java b/libraries/health/runners/microbenchmark/src/android/platform/test/microbenchmark/Microbenchmark.java
index 78a03e8..ad12eb8 100644
--- a/libraries/health/runners/microbenchmark/src/android/platform/test/microbenchmark/Microbenchmark.java
+++ b/libraries/health/runners/microbenchmark/src/android/platform/test/microbenchmark/Microbenchmark.java
@@ -15,9 +15,16 @@
*/
package android.platform.test.microbenchmark;
+import static android.content.Context.BATTERY_SERVICE;
+import static android.os.BatteryManager.BATTERY_PROPERTY_CAPACITY;
+import static android.os.BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER;
+
+import android.os.BatteryManager;
import android.os.Bundle;
+import android.os.SystemClock;
import android.platform.test.composer.Iterate;
import android.platform.test.rule.TracePointRule;
+import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.test.InstrumentationRegistry;
@@ -50,6 +57,8 @@
*/
public class Microbenchmark extends BlockJUnit4ClassRunner {
+ private static final String LOG_TAG = Microbenchmark.class.getSimpleName();
+
@VisibleForTesting static final String ITERATION_SEP_OPTION = "iteration-separator";
@VisibleForTesting static final String ITERATION_SEP_DEFAULT = "$";
// A constant to indicate that the iteration number is not set.
@@ -60,12 +69,28 @@
@Override
public void evaluate() throws Throwable {}
};
+ private static final String MIN_BATTERY_LEVEL_OPTION = "min-battery";
+
+ // Options for aligning with the battery charge (coulomb) counter for power tests. We want to
+ // start microbenchmarks just after the coulomb counter has decremented to account for the
+ // counter being quantized. The counter most accurately reflects the true value just after it
+ // decrements.
+ private static final String ALIGN_WITH_CHARGE_COUNTER_OPTION = "align-with-charge-counter";
+ private static final String COUNTER_DECREMENT_TIMEOUT_OPTION = "counter-decrement-timeout_ms";
+
+ private static final String TERMINATE_ON_TEST_FAIL_OPTION = "terminate-on-test-fail";
private final String mIterationSep;
private final Bundle mArguments;
private final boolean mRenameIterations;
+ private final int mMinBatteryLevel;
+ private final int mCounterDecrementTimeoutMs;
+ private final boolean mAlignWithChargeCounter;
+ private final boolean mTerminateOnTestFailure;
private final Map<Description, Integer> mIterations = new HashMap<>();
+ private final BatteryManager mBatteryManager;
+
/**
* Called reflectively on classes annotated with {@code @RunWith(Microbenchmark.class)}.
*/
@@ -73,9 +98,7 @@
this(klass, InstrumentationRegistry.getArguments());
}
- /**
- * Do not call. Called explicitly from tests to provide an arguments.
- */
+ /** Do not call. Called explicitly from tests to provide an arguments. */
@VisibleForTesting
Microbenchmark(Class<?> klass, Bundle arguments) throws InitializationError {
super(klass);
@@ -86,6 +109,48 @@
arguments.containsKey(ITERATION_SEP_OPTION)
? arguments.getString(ITERATION_SEP_OPTION)
: ITERATION_SEP_DEFAULT;
+ mMinBatteryLevel = Integer.parseInt(arguments.getString(MIN_BATTERY_LEVEL_OPTION, "-1"));
+ mCounterDecrementTimeoutMs =
+ Integer.parseInt(arguments.getString(COUNTER_DECREMENT_TIMEOUT_OPTION, "30000"));
+ mAlignWithChargeCounter =
+ Boolean.parseBoolean(
+ arguments.getString(ALIGN_WITH_CHARGE_COUNTER_OPTION, "false"));
+
+ mTerminateOnTestFailure =
+ Boolean.parseBoolean(
+ arguments.getString(TERMINATE_ON_TEST_FAIL_OPTION, "false"));
+
+ // Get the battery manager for later use.
+ mBatteryManager =
+ (BatteryManager)
+ InstrumentationRegistry.getContext().getSystemService(BATTERY_SERVICE);
+ }
+
+ @Override
+ public void run(final RunNotifier notifier) {
+ if (mAlignWithChargeCounter) {
+ // Try to wait until the coulomb counter has just decremented to start the test.
+ int startChargeCounter = getBatteryChargeCounter();
+ long startTimestamp = SystemClock.uptimeMillis();
+ while (startChargeCounter == getBatteryChargeCounter()) {
+ if (SystemClock.uptimeMillis() - startTimestamp > mCounterDecrementTimeoutMs) {
+ Log.d(
+ LOG_TAG,
+ "Timed out waiting for the counter to change. Continuing anyway.");
+ break;
+ } else {
+ Log.d(
+ LOG_TAG,
+ String.format(
+ "Charge counter still reads: %d. Waiting.",
+ startChargeCounter));
+ SystemClock.sleep(getCounterPollingInterval());
+ }
+ }
+ }
+ Log.d(LOG_TAG, String.format("The charge counter reads: %d.", getBatteryChargeCounter()));
+
+ super.run(notifier);
}
/**
@@ -228,6 +293,10 @@
*/
@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
+ if (isBatteryLevelTooLow()) {
+ throw new TerminateEarlyException("battery level is below threshold.");
+ }
+
// Update the number of iterations this method has been run.
if (mRenameIterations) {
Description original = super.describeChild(method);
@@ -261,6 +330,9 @@
eachNotifier.fireTestStarted();
eachNotifier.addFailure(e);
eachNotifier.fireTestFinished();
+ if(mTerminateOnTestFailure) {
+ throw new TerminateEarlyException("test failed.");
+ }
return;
}
@@ -271,14 +343,17 @@
statement = withAfters(method, test, statement);
statement = withRules(method, test, statement);
+ boolean testFailed = false;
// Fire test events from inside to exclude "no metric" methods.
eachNotifier.fireTestStarted();
try {
statement.evaluate();
} catch (AssumptionViolatedException e) {
eachNotifier.addFailedAssumption(e);
+ testFailed = true;
} catch (Throwable e) {
eachNotifier.addFailure(e);
+ testFailed = true;
} finally {
eachNotifier.fireTestFinished();
}
@@ -292,9 +367,44 @@
}
} catch (AssumptionViolatedException e) {
eachNotifier.addFailedAssumption(e);
+ testFailed = true;
} catch (Throwable e) {
eachNotifier.addFailure(e);
+ testFailed = true;
+ }
+
+ if(mTerminateOnTestFailure && testFailed) {
+ throw new TerminateEarlyException("test failed.");
}
}
}
+
+ /* Checks if the battery level is below the specified level where the test should terminate. */
+ @VisibleForTesting
+ public boolean isBatteryLevelTooLow() {
+ return mBatteryManager.getIntProperty(BATTERY_PROPERTY_CAPACITY) < mMinBatteryLevel;
+ }
+
+ /* Gets the current battery charge counter (coulomb counter). */
+ @VisibleForTesting
+ public int getBatteryChargeCounter() {
+ return mBatteryManager.getIntProperty(BATTERY_PROPERTY_CHARGE_COUNTER);
+ }
+
+ /* Gets the polling interval to check for changes in the battery charge counter. */
+ @VisibleForTesting
+ public long getCounterPollingInterval() {
+ return 100;
+ }
+
+ /**
+ * A {@code RuntimeException} class for terminating test runs early for some specified reason.
+ */
+ @VisibleForTesting
+ static class TerminateEarlyException extends RuntimeException {
+ public TerminateEarlyException(String message) {
+ super(String.format("Terminating early because %s", message));
+ }
+ }
+
}
diff --git a/libraries/health/runners/microbenchmark/tests/Android.bp b/libraries/health/runners/microbenchmark/tests/Android.bp
index 3e7c103..df83ba5 100644
--- a/libraries/health/runners/microbenchmark/tests/Android.bp
+++ b/libraries/health/runners/microbenchmark/tests/Android.bp
@@ -18,6 +18,7 @@
sdk_version: "test_current",
static_libs: [
"microbenchmark-device-lib",
+ "mockito-target",
"truth-prebuilt",
],
srcs: ["src/**/*.java"],
diff --git a/libraries/health/runners/microbenchmark/tests/src/android/platform/test/microbenchmark/MicrobenchmarkTest.java b/libraries/health/runners/microbenchmark/tests/src/android/platform/test/microbenchmark/MicrobenchmarkTest.java
index 9a43f8c..a497a42 100644
--- a/libraries/health/runners/microbenchmark/tests/src/android/platform/test/microbenchmark/MicrobenchmarkTest.java
+++ b/libraries/health/runners/microbenchmark/tests/src/android/platform/test/microbenchmark/MicrobenchmarkTest.java
@@ -16,8 +16,15 @@
package android.platform.test.microbenchmark;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import android.os.Bundle;
+import android.os.SystemClock;
+import android.platform.test.microbenchmark.Microbenchmark.TerminateEarlyException;
import android.platform.test.rule.TracePointRule;
import org.junit.After;
@@ -28,9 +35,13 @@
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.RunWith;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
import org.junit.runners.JUnit4;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
@@ -220,6 +231,152 @@
.inOrder();
}
+ /** Test that the microbenchmark will terminate if the battery is too low. */
+ @Test
+ public void testBatteryIsTooLow() throws InitializationError {
+ Microbenchmark runner = Mockito.spy(new Microbenchmark(LoggingTest.class, new Bundle()));
+ doReturn(true).when(runner).isBatteryLevelTooLow();
+
+ RunNotifier notifier = Mockito.mock(RunNotifier.class);
+ runner.run(notifier);
+
+ ArgumentCaptor<Failure> failureCaptor = ArgumentCaptor.forClass(Failure.class);
+ verify(notifier).fireTestFailure(failureCaptor.capture());
+
+ Failure failure = failureCaptor.getValue();
+ Throwable throwable = failure.getException();
+ assertTrue(
+ String.format(
+ "Exception was not a TerminateEarlyException. Instead, it was: %s",
+ throwable.getClass()),
+ throwable instanceof TerminateEarlyException);
+ assertThat(throwable)
+ .hasMessageThat()
+ .matches("Terminating early because battery level is below threshold.");
+ }
+
+ /** Test that the microbenchmark will align starting with the battery charge counter. */
+ @Test
+ public void testAlignWithBatteryChargeCounter() throws InitializationError {
+ Bundle args = new Bundle();
+ args.putString("align-with-charge-counter", "true");
+ args.putString("counter-decrement-timeout_ms", "5000");
+
+ Microbenchmark runner = Mockito.spy(new Microbenchmark(LoggingTest.class, args));
+ doReturn(99999)
+ .doReturn(99999)
+ .doReturn(99999)
+ .doReturn(88888)
+ .when(runner)
+ .getBatteryChargeCounter();
+ doReturn(10L).when(runner).getCounterPollingInterval();
+
+ RunNotifier notifier = Mockito.mock(RunNotifier.class);
+
+ Thread thread =
+ new Thread(
+ new Runnable() {
+ public void run() {
+ runner.run(notifier);
+ }
+ });
+
+ thread.start();
+ SystemClock.sleep(20);
+ verify(notifier, never()).fireTestStarted(any(Description.class));
+ SystemClock.sleep(20);
+ verify(notifier).fireTestStarted(any(Description.class));
+ }
+
+ /** Test that the microbenchmark counter alignment will time out if there's no change. */
+ @Test
+ public void testAlignWithBatteryChargeCounter_timesOut() throws InitializationError {
+ Bundle args = new Bundle();
+ args.putString("align-with-charge-counter", "true");
+ args.putString("counter-decrement-timeout_ms", "30");
+
+ Microbenchmark runner = Mockito.spy(new Microbenchmark(LoggingTest.class, args));
+ doReturn(99999).when(runner).getBatteryChargeCounter();
+ doReturn(10L).when(runner).getCounterPollingInterval();
+
+ RunNotifier notifier = Mockito.mock(RunNotifier.class);
+
+ Thread thread =
+ new Thread(
+ new Runnable() {
+ public void run() {
+ runner.run(notifier);
+ }
+ });
+
+ thread.start();
+ SystemClock.sleep(20);
+ verify(notifier, never()).fireTestStarted(any(Description.class));
+ SystemClock.sleep(30);
+ verify(notifier).fireTestStarted(any(Description.class));
+ }
+
+ /**
+ * Test successive iteration will not be executed when the terminate on test fail
+ * option is enabled.
+ */
+ @Test
+ public void testTerminateOnTestFailOptionEnabled() throws InitializationError {
+ Bundle args = new Bundle();
+ args.putString("iterations", "2");
+ args.putString("rename-iterations", "false");
+ args.putString("terminate-on-test-fail", "true");
+ LoggingMicrobenchmark loggingRunner = new LoggingMicrobenchmark(
+ LoggingFailedTest.class, args);
+ loggingRunner.setOperationLog(new ArrayList<String>());
+ Result result = new JUnitCore().run(loggingRunner);
+ assertThat(result.wasSuccessful()).isFalse();
+ assertThat(loggingRunner.getOperationLog())
+ .containsExactly(
+ "before",
+ "tight before",
+ "begin: testMethod("
+ + "android.platform.test.microbenchmark.MicrobenchmarkTest"
+ + "$LoggingFailedTest)",
+ "end",
+ "after")
+ .inOrder();
+ }
+
+ /**
+ * Test successive iteration will be executed when the terminate on test fail
+ * option is disabled.
+ */
+ @Test
+ public void testTerminateOnTestFailOptionDisabled() throws InitializationError {
+ Bundle args = new Bundle();
+ args.putString("iterations", "2");
+ args.putString("rename-iterations", "false");
+ args.putString("terminate-on-test-fail", "false");
+ LoggingMicrobenchmark loggingRunner = new LoggingMicrobenchmark(
+ LoggingFailedTest.class, args);
+ loggingRunner.setOperationLog(new ArrayList<String>());
+ Result result = new JUnitCore().run(loggingRunner);
+ assertThat(result.wasSuccessful()).isFalse();
+ assertThat(loggingRunner.getOperationLog())
+ .containsExactly(
+ "before",
+ "tight before",
+ "begin: testMethod("
+ + "android.platform.test.microbenchmark.MicrobenchmarkTest"
+ + "$LoggingFailedTest)",
+ "end",
+ "after",
+ "before",
+ "tight before",
+ "begin: testMethod("
+ + "android.platform.test.microbenchmark.MicrobenchmarkTest"
+ + "$LoggingFailedTest)",
+ "end",
+ "after")
+ .inOrder();
+ }
+
/**
* An extensions of the {@link Microbenchmark} runner that logs the start and end of collecting
* traces. It also passes the operation log to the provided test {@code Class}, if it is a
@@ -315,4 +472,11 @@
}
}
}
+
+ public static class LoggingFailedTest extends LoggingTest {
+ @Test
+ public void testMethod() {
+ throw new RuntimeException("I failed.");
+ }
+ }
}
diff --git a/tests/functional/devicehealthchecks/assets/bug_map b/tests/functional/devicehealthchecks/assets/bug_map
index 1448d8d..86974f6 100644
--- a/tests/functional/devicehealthchecks/assets/bug_map
+++ b/tests/functional/devicehealthchecks/assets/bug_map
@@ -14,9 +14,13 @@
system_app_crash com.google.android.apps.youtube.music.mediabrowser.MusicBrowserService.a 157917208
system_app_crash android.database.sqlite.SQLiteCloseable.acquireReference 159658068
system_app_crash com.google.android.gms.backup.component.D2dTransportService 31428310
-system_app_native_crash com.google.android.apps.safetyhub 154358781
+system_app_crash Unable\sto\sinstantiate\sapplication\sorg.chromium.chrome.browser.ChromeApplication 161275381
+system_app_crash com.google.android.as.*\s.*\s.*\s.*\s*.*act=android.net.wifi.STATE_CHANGE 161559360
+system_app_crash com.google.android.googlequicksearchbox:search.*\s.*\s.*\s.*\s.*11.26.*\s.*\s.*\s.*\s.*\s\s.*ConcurrentModificationException.*\s.*\s.*\s.*apps.gsa.shared.util.debug.a.g.a 171971925
+system_app_native_crash com.google.android.apps.safetyhub[\s\S]*Scudo\sERROR:\smisaligned\spointer\swhen\sdeallocating\saddress 154358781
system_app_native_crash com.google.android.providers.media.module 154416156
system_server_crash void.com.android.server.location.gnss.GnssBatchingProvider.enable 159504970
SYSTEM_TOMBSTONE android.hardware.vibrator-service.drv2624 151884322
SYSTEM_TOMBSTONE /vendor/bin/hw/android.hardware.gnss@1.0-service-qti 129282808
SYSTEM_TOMBSTONE /system/product/priv-app/SafetyHubLprPrebuilt/SafetyHubLprPrebuilt.apk 158504050
+SYSTEM_TOMBSTONE AsyncTask\s+#1\s+>>>\s+com.android.nfc\s+<<<[\S\s]*nfc_main_handle_hal_evt 179110580