summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/ApkVerityTest/Android.bp34
-rw-r--r--tests/ApkVerityTest/AndroidTest.xml41
-rw-r--r--tests/ApkVerityTest/ApkVerityTestApp/Android.bp29
-rw-r--r--tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml23
-rw-r--r--tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.xml25
-rw-r--r--tests/ApkVerityTest/ApkVerityTestApp/feature_split/src/com/android/apkverity/feature_x/DummyActivity.java (renamed from tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/MainActivity.java)23
-rw-r--r--tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java22
-rw-r--r--tests/ApkVerityTest/block_device_writer/Android.bp30
-rw-r--r--tests/ApkVerityTest/block_device_writer/block_device_writer.cpp189
-rw-r--r--tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java496
-rw-r--r--tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java140
-rw-r--r--tests/ApkVerityTest/testdata/Android.bp77
-rw-r--r--tests/ApkVerityTest/testdata/ApkVerityTestApp.dmbin0 -> 237348 bytes
-rw-r--r--tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dmbin0 -> 237025 bytes
-rw-r--r--tests/ApkVerityTest/testdata/ApkVerityTestCert.derbin0 -> 1330 bytes
-rw-r--r--tests/ApkVerityTest/testdata/ApkVerityTestCert.pem30
-rw-r--r--tests/ApkVerityTest/testdata/ApkVerityTestKey.pem52
-rw-r--r--tests/ApkVerityTest/testdata/README.md13
-rw-r--r--tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml2
-rw-r--r--tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java177
-rw-r--r--tests/Codegen/Android.bp25
-rw-r--r--tests/Codegen/AndroidManifest.xml26
-rw-r--r--tests/Codegen/AndroidTest.xml (renamed from tests/RollbackTest/TestApp/res_v1/values-anydpi/values.xml)10
-rw-r--r--tests/Codegen/OWNERS1
-rwxr-xr-xtests/Codegen/runTest.sh27
-rw-r--r--tests/Codegen/src/com/android/codegentest/MyDateParcelling.java51
-rw-r--r--tests/Codegen/src/com/android/codegentest/SampleDataClass.aidl18
-rw-r--r--tests/Codegen/src/com/android/codegentest/SampleDataClass.java1808
-rw-r--r--tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java223
-rw-r--r--tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java186
-rw-r--r--tests/FeatureSplit/feature1/Android.bp2
-rw-r--r--tests/FeatureSplit/feature1/AndroidManifest.xml1
-rw-r--r--tests/FeatureSplit/feature1/res/layout/included.xml3
-rw-r--r--tests/FeatureSplit/feature1/res/values/values.xml3
-rw-r--r--tests/FeatureSplit/feature2/res/values/values.xml3
-rw-r--r--tests/GamePerformance/AndroidManifest.xml7
-rw-r--r--tests/GamePerformance/res/drawable/animation.xml29
-rw-r--r--tests/GamePerformance/res/drawable/digit_0.pngbin0 -> 1335 bytes
-rw-r--r--tests/GamePerformance/res/drawable/digit_1.pngbin0 -> 428 bytes
-rw-r--r--tests/GamePerformance/res/drawable/digit_2.pngbin0 -> 1047 bytes
-rw-r--r--tests/GamePerformance/res/drawable/digit_3.pngbin0 -> 1275 bytes
-rw-r--r--tests/GamePerformance/res/drawable/digit_4.pngbin0 -> 613 bytes
-rw-r--r--tests/GamePerformance/res/drawable/digit_5.pngbin0 -> 945 bytes
-rw-r--r--tests/GamePerformance/res/drawable/digit_6.pngbin0 -> 1488 bytes
-rw-r--r--tests/GamePerformance/res/drawable/digit_7.pngbin0 -> 949 bytes
-rw-r--r--tests/GamePerformance/res/drawable/digit_8.pngbin0 -> 1440 bytes
-rw-r--r--tests/GamePerformance/res/drawable/digit_9.pngbin0 -> 1501 bytes
-rw-r--r--tests/GamePerformance/res/drawable/logo.pngbin0 -> 48689 bytes
-rw-r--r--tests/GamePerformance/src/android/gameperformance/BaseTest.java152
-rw-r--r--tests/GamePerformance/src/android/gameperformance/CPULoadThread.java61
-rw-r--r--tests/GamePerformance/src/android/gameperformance/ControlsTest.java73
-rw-r--r--tests/GamePerformance/src/android/gameperformance/CustomControlView.java128
-rw-r--r--tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java91
-rw-r--r--tests/GamePerformance/src/android/gameperformance/DeviceCallsOpenGLTest.java60
-rw-r--r--tests/GamePerformance/src/android/gameperformance/FillRateOpenGLTest.java93
-rw-r--r--tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java70
-rw-r--r--tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java50
-rw-r--r--tests/GamePerformance/src/android/gameperformance/OpenGLTest.java76
-rw-r--r--tests/GamePerformance/src/android/gameperformance/OpenGLUtils.java92
-rw-r--r--tests/GamePerformance/src/android/gameperformance/RenderPatch.java150
-rw-r--r--tests/GamePerformance/src/android/gameperformance/RenderPatchAnimation.java101
-rw-r--r--tests/GamePerformance/src/android/gameperformance/RenderPatchOpenGLTest.java188
-rw-r--r--tests/GamePerformance/src/android/gameperformance/TriangleCountOpenGLTest.java66
-rw-r--r--tests/JobSchedulerPerfTests/Android.bp26
-rw-r--r--tests/JobSchedulerPerfTests/AndroidManifest.xml (renamed from tests/RollbackTest/TestApp/res_v2/values-anydpi/values.xml)16
-rw-r--r--tests/JobSchedulerPerfTests/AndroidTest.xml28
-rw-r--r--tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java156
-rw-r--r--tests/PackageWatchdog/Android.bp1
-rw-r--r--tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java634
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java24
-rw-r--r--tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java24
-rw-r--r--tests/RollbackTest/Android.bp86
-rw-r--r--tests/RollbackTest/MultiUserRollbackTest.xml (renamed from tests/RollbackTest/TestApp/res_v1/values/values.xml)14
-rw-r--r--tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java117
-rw-r--r--tests/RollbackTest/RollbackTest.xml5
-rw-r--r--tests/RollbackTest/RollbackTest/AndroidManifest.xml2
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/LocalIntentSender.java60
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java111
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java78
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java774
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java547
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java213
-rw-r--r--tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java61
-rw-r--r--tests/RollbackTest/TEST_MAPPING3
-rw-r--r--tests/RollbackTest/TestApp/ACrashingV2.xml36
-rw-r--r--tests/RollbackTest/TestApp/Av1.xml35
-rw-r--r--tests/RollbackTest/TestApp/Av2.xml35
-rw-r--r--tests/RollbackTest/TestApp/Bv1.xml35
-rw-r--r--tests/RollbackTest/TestApp/Bv2.xml35
-rw-r--r--tests/RollbackTest/TestApp/res_v2/values/values.xml20
-rw-r--r--tests/RollbackTest/TestApp/res_v3/values-anydpi/values.xml19
-rw-r--r--tests/RollbackTest/TestApp/res_v3/values/values.xml20
-rw-r--r--tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java46
-rw-r--r--tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/ProcessUserData.java111
-rw-r--r--tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java15
-rw-r--r--tests/WindowlessWmTest/Android.bp22
-rw-r--r--tests/WindowlessWmTest/AndroidManifest.xml (renamed from tests/RollbackTest/TestApp/Av3.xml)23
-rw-r--r--tests/WindowlessWmTest/src/com/android/test/viewembed/WindowlessWmTest.java78
-rw-r--r--tests/utils/testutils/java/android/os/test/TestLooper.java38
-rw-r--r--tests/utils/testutils/java/android/view/test/InsetsModeSession.java2
115 files changed, 7053 insertions, 2134 deletions
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
new file mode 100644
index 000000000000..adcbb4287dd0
--- /dev/null
+++ b/tests/ApkVerityTest/Android.bp
@@ -0,0 +1,34 @@
+// 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.
+
+java_test_host {
+ name: "ApkVerityTests",
+ srcs: ["src/**/*.java"],
+ libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
+ test_suites: ["general-tests"],
+ target_required: [
+ "block_device_writer_module",
+ "ApkVerityTestApp",
+ "ApkVerityTestAppSplit",
+ ],
+ data: [
+ ":ApkVerityTestCertDer",
+ ":ApkVerityTestAppFsvSig",
+ ":ApkVerityTestAppDm",
+ ":ApkVerityTestAppDmFsvSig",
+ ":ApkVerityTestAppSplitFsvSig",
+ ":ApkVerityTestAppSplitDm",
+ ":ApkVerityTestAppSplitDmFsvSig",
+ ],
+}
diff --git a/tests/ApkVerityTest/AndroidTest.xml b/tests/ApkVerityTest/AndroidTest.xml
new file mode 100644
index 000000000000..73779cbd1a87
--- /dev/null
+++ b/tests/ApkVerityTest/AndroidTest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="APK fs-verity integration/regression test">
+ <option name="test-suite-tag" value="apct" />
+
+ <!-- This test requires root to write against block device. -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- Disable package verifier prevents it holding the target APK's fd that prevents cache
+ eviction. -->
+ <option name="set-global-setting" key="package_verifier_enable" value="0" />
+ <option name="restore-settings" value="true" />
+
+ <!-- Skip in order to prevent reboot that confuses the test flow. -->
+ <option name="force-skip-system-props" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
+ <option name="push" value="ApkVerityTestCert.der->/data/local/tmp/ApkVerityTestCert.der" />
+ </target_preparer>
+
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="ApkVerityTests.jar" />
+ </test>
+</configuration>
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/Android.bp b/tests/ApkVerityTest/ApkVerityTestApp/Android.bp
new file mode 100644
index 000000000000..69632b215822
--- /dev/null
+++ b/tests/ApkVerityTest/ApkVerityTestApp/Android.bp
@@ -0,0 +1,29 @@
+// 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.
+
+android_test_helper_app {
+ name: "ApkVerityTestApp",
+ manifest: "AndroidManifest.xml",
+ srcs: ["src/**/*.java"],
+}
+
+android_test_helper_app {
+ name: "ApkVerityTestAppSplit",
+ manifest: "feature_split/AndroidManifest.xml",
+ srcs: ["src/**/*.java"],
+ aaptflags: [
+ "--custom-package com.android.apkverity.feature_x",
+ "--package-id 0x80",
+ ],
+}
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml b/tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml
new file mode 100644
index 000000000000..0b3ff77c2cdf
--- /dev/null
+++ b/tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.apkverity">
+ <application>
+ <activity android:name=".DummyActivity"/>
+ </application>
+</manifest>
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.xml b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.xml
new file mode 100644
index 000000000000..3f1a4f3a26a1
--- /dev/null
+++ b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.apkverity"
+ android:isFeatureSplit="true"
+ split="feature_x">
+ <application>
+ <activity android:name=".feature_x.DummyActivity"/>
+ </application>
+</manifest>
diff --git a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/MainActivity.java b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/src/com/android/apkverity/feature_x/DummyActivity.java
index 9f1a0609d3f1..0f694c293330 100644
--- a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/MainActivity.java
+++ b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/src/com/android/apkverity/feature_x/DummyActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,24 +14,9 @@
* limitations under the License.
*/
-package com.android.tests.rollback.testapp;
+package com.android.apkverity.feature_x;
import android.app.Activity;
-import android.os.Bundle;
-/**
- * A test app for testing apk rollback support.
- */
-public class MainActivity extends Activity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- try {
- new ProcessUserData().processUserData(this);
- } catch (ProcessUserData.UserDataException e) {
- throw new AssertionError("Failed to process app user data", e);
- }
- }
-}
+/** Dummy class just to generate some dex */
+public class DummyActivity extends Activity {}
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java b/tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java
new file mode 100644
index 000000000000..837c7be37504
--- /dev/null
+++ b/tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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 com.android.apkverity;
+
+import android.app.Activity;
+
+/** Dummy class just to generate some dex */
+public class DummyActivity extends Activity {}
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
new file mode 100644
index 000000000000..deed3a00d2fe
--- /dev/null
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -0,0 +1,30 @@
+// 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.
+
+// This is a cc_test just because it supports test_suites. This should be converted to something
+// like cc_binary_test_helper once supported.
+cc_test {
+ // Depending on how the test runs, the executable may be uploaded to different location.
+ // Before the bug in the file pusher is fixed, workaround by making the name unique.
+ // See b/124718249#comment12.
+ name: "block_device_writer_module",
+ stem: "block_device_writer",
+
+ srcs: ["block_device_writer.cpp"],
+ cflags: ["-Wall", "-Werror", "-Wextra", "-g"],
+ shared_libs: ["libbase", "libutils"],
+
+ test_suites: ["general-tests"],
+ gtest: false,
+}
diff --git a/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp b/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
new file mode 100644
index 000000000000..b0c7251e77f5
--- /dev/null
+++ b/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
@@ -0,0 +1,189 @@
+/*
+ * 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.
+ */
+#include <cassert>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <memory>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fiemap.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/unique_fd.h>
+
+// This program modifies a file at given offset, but directly against the block
+// device, purposely to bypass the filesystem. Note that the change on block
+// device may not reflect the same way when read from filesystem, for example,
+// when the file is encrypted on disk.
+//
+// Only one byte is supported for now just so that we don't need to handle the
+// case when the range crosses different "extents".
+//
+// References:
+// https://www.kernel.org/doc/Documentation/filesystems/fiemap.txt
+// https://git.kernel.org/pub/scm/fs/xfs/xfsprogs-dev.git/tree/io/fiemap.c
+
+ssize_t get_logical_block_size(const char* block_device) {
+ android::base::unique_fd fd(open(block_device, O_RDONLY));
+ if (fd.get() < 0) {
+ fprintf(stderr, "open %s failed\n", block_device);
+ return -1;
+ }
+
+ int size;
+ if (ioctl(fd, BLKSSZGET, &size) < 0) {
+ fprintf(stderr, "ioctl(BLKSSZGET) failed: %s\n", strerror(errno));
+ return -1;
+ }
+ return size;
+}
+
+int64_t get_physical_offset(const char* file_name, uint64_t byte_offset) {
+ android::base::unique_fd fd(open(file_name, O_RDONLY));
+ if (fd.get() < 0) {
+ fprintf(stderr, "open %s failed\n", file_name);
+ return -1;
+ }
+
+ const int map_size = sizeof(struct fiemap) + sizeof(struct fiemap_extent);
+ char fiemap_buffer[map_size] = {0};
+ struct fiemap* fiemap = reinterpret_cast<struct fiemap*>(&fiemap_buffer);
+
+ fiemap->fm_flags = FIEMAP_FLAG_SYNC;
+ fiemap->fm_start = byte_offset;
+ fiemap->fm_length = 1;
+ fiemap->fm_extent_count = 1;
+
+ int ret = ioctl(fd.get(), FS_IOC_FIEMAP, fiemap);
+ if (ret < 0) {
+ fprintf(stderr, "ioctl(FS_IOC_FIEMAP) failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (fiemap->fm_mapped_extents != 1) {
+ fprintf(stderr, "fm_mapped_extents != 1 (is %d)\n",
+ fiemap->fm_mapped_extents);
+ return -1;
+ }
+
+ struct fiemap_extent* extent = &fiemap->fm_extents[0];
+ printf(
+ "logical offset: %llu, physical offset: %llu, length: %llu, "
+ "flags: %x\n",
+ extent->fe_logical, extent->fe_physical, extent->fe_length,
+ extent->fe_flags);
+ if (extent->fe_flags & (FIEMAP_EXTENT_UNKNOWN |
+ FIEMAP_EXTENT_UNWRITTEN)) {
+ fprintf(stderr, "Failed to locate physical offset safely\n");
+ return -1;
+ }
+
+ return extent->fe_physical + (byte_offset - extent->fe_logical);
+}
+
+int read_block_from_device(const char* device_path, uint64_t block_offset,
+ ssize_t block_size, char* block_buffer) {
+ assert(block_offset % block_size == 0);
+ android::base::unique_fd fd(open(device_path, O_RDONLY | O_DIRECT));
+ if (fd.get() < 0) {
+ fprintf(stderr, "open %s failed\n", device_path);
+ return -1;
+ }
+
+ ssize_t retval =
+ TEMP_FAILURE_RETRY(pread(fd, block_buffer, block_size, block_offset));
+ if (retval != block_size) {
+ fprintf(stderr, "read returns error or incomplete result (%zu): %s\n",
+ retval, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+int write_block_to_device(const char* device_path, uint64_t block_offset,
+ ssize_t block_size, char* block_buffer) {
+ assert(block_offset % block_size == 0);
+ android::base::unique_fd fd(open(device_path, O_WRONLY | O_DIRECT));
+ if (fd.get() < 0) {
+ fprintf(stderr, "open %s failed\n", device_path);
+ return -1;
+ }
+
+ ssize_t retval = TEMP_FAILURE_RETRY(
+ pwrite(fd.get(), block_buffer, block_size, block_offset));
+ if (retval != block_size) {
+ fprintf(stderr, "write returns error or incomplete result (%zu): %s\n",
+ retval, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+int main(int argc, const char** argv) {
+ if (argc != 4) {
+ fprintf(stderr,
+ "Usage: %s block_dev filename byte_offset\n"
+ "\n"
+ "This program bypasses filesystem and damages the specified byte\n"
+ "at the physical position on <block_dev> corresponding to the\n"
+ "logical byte location in <filename>.\n",
+ argv[0]);
+ return -1;
+ }
+
+ const char* block_device = argv[1];
+ const char* file_name = argv[2];
+ uint64_t byte_offset = strtoull(argv[3], nullptr, 10);
+
+ ssize_t block_size = get_logical_block_size(block_device);
+ if (block_size < 0) {
+ return -1;
+ }
+
+ int64_t physical_offset_signed = get_physical_offset(file_name, byte_offset);
+ if (physical_offset_signed < 0) {
+ return -1;
+ }
+
+ uint64_t physical_offset = static_cast<uint64_t>(physical_offset_signed);
+ uint64_t offset_within_block = physical_offset % block_size;
+ uint64_t physical_block_offset = physical_offset - offset_within_block;
+
+ // Direct I/O requires aligned buffer
+ std::unique_ptr<char> buf(static_cast<char*>(
+ aligned_alloc(block_size /* alignment */, block_size /* size */)));
+
+ if (read_block_from_device(block_device, physical_block_offset, block_size,
+ buf.get()) < 0) {
+ return -1;
+ }
+ char* p = buf.get() + offset_within_block;
+ printf("before: %hhx\n", *p);
+ *p ^= 0xff;
+ printf("after: %hhx\n", *p);
+ if (write_block_to_device(block_device, physical_block_offset, block_size,
+ buf.get()) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
new file mode 100644
index 000000000000..761c5ceb2413
--- /dev/null
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -0,0 +1,496 @@
+/*
+ * 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 com.android.apkverity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.RootPermissionTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * This test makes sure app installs with fs-verity signature, and on-access verification works.
+ *
+ * <p>When an app is installed, all or none of the files should have their corresponding .fsv_sig
+ * signature file. Otherwise, install will fail.
+ *
+ * <p>Once installed, file protected by fs-verity is verified by kernel every time a block is loaded
+ * from disk to memory. The file is immutable by design, enforced by filesystem.
+ *
+ * <p>In order to make sure a block of the file is readable only if the underlying block on disk
+ * stay intact, the test needs to bypass the filesystem and tampers with the corresponding physical
+ * address against the block device.
+ *
+ * <p>Requirements to run this test:
+ * <ul>
+ * <li>Device is rootable</li>
+ * <li>The filesystem supports fs-verity</li>
+ * <li>The feature flag is enabled</li>
+ * </ul>
+ */
+@RootPermissionTest
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ApkVerityTest extends BaseHostJUnit4Test {
+ private static final String TARGET_PACKAGE = "com.android.apkverity";
+
+ private static final String BASE_APK = "ApkVerityTestApp.apk";
+ private static final String BASE_APK_DM = "ApkVerityTestApp.dm";
+ private static final String SPLIT_APK = "ApkVerityTestAppSplit.apk";
+ private static final String SPLIT_APK_DM = "ApkVerityTestAppSplit.dm";
+
+ private static final String INSTALLED_BASE_APK = "base.apk";
+ private static final String INSTALLED_BASE_DM = "base.dm";
+ private static final String INSTALLED_SPLIT_APK = "split_feature_x.apk";
+ private static final String INSTALLED_SPLIT_DM = "split_feature_x.dm";
+ private static final String INSTALLED_BASE_APK_FSV_SIG = "base.apk.fsv_sig";
+ private static final String INSTALLED_BASE_DM_FSV_SIG = "base.dm.fsv_sig";
+ private static final String INSTALLED_SPLIT_APK_FSV_SIG = "split_feature_x.apk.fsv_sig";
+ private static final String INSTALLED_SPLIT_DM_FSV_SIG = "split_feature_x.dm.fsv_sig";
+
+ private static final String DAMAGING_EXECUTABLE = "/data/local/tmp/block_device_writer";
+ private static final String CERT_PATH = "/data/local/tmp/ApkVerityTestCert.der";
+
+ private static final String APK_VERITY_STANDARD_MODE = "2";
+
+ /** Only 4K page is supported by fs-verity currently. */
+ private static final int FSVERITY_PAGE_SIZE = 4096;
+
+ private ITestDevice mDevice;
+ private String mKeyId;
+
+ @Before
+ public void setUp() throws DeviceNotAvailableException {
+ mDevice = getDevice();
+
+ String apkVerityMode = mDevice.getProperty("ro.apk_verity.mode");
+ assumeTrue(APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
+
+ mKeyId = expectRemoteCommandToSucceed(
+ "mini-keyctl padd asymmetric fsv_test .fs-verity < " + CERT_PATH).trim();
+ if (!mKeyId.matches("^\\d+$")) {
+ String keyId = mKeyId;
+ mKeyId = null;
+ fail("Key ID is not decimal: " + keyId);
+ }
+
+ uninstallPackage(TARGET_PACKAGE);
+ }
+
+ @After
+ public void tearDown() throws DeviceNotAvailableException {
+ uninstallPackage(TARGET_PACKAGE);
+
+ if (mKeyId != null) {
+ expectRemoteCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity");
+ }
+ }
+
+ @Test
+ public void testFsverityKernelSupports() throws DeviceNotAvailableException {
+ ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data");
+ expectRemoteCommandToSucceed("test -f /sys/fs/" + mountPoint.type + "/features/verity");
+ }
+
+ @Test
+ public void testInstallBase() throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFileAndSignature(BASE_APK)
+ .run();
+ assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+
+ verifyInstalledFiles(
+ INSTALLED_BASE_APK,
+ INSTALLED_BASE_APK_FSV_SIG);
+ verifyInstalledFilesHaveFsverity();
+ }
+
+ @Test
+ public void testInstallBaseWithWrongSignature()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFile(BASE_APK)
+ .addFile(SPLIT_APK_DM + ".fsv_sig",
+ BASE_APK + ".fsv_sig")
+ .runExpectingFailure();
+ }
+
+ @Test
+ public void testInstallBaseWithSplit()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFileAndSignature(BASE_APK)
+ .addFileAndSignature(SPLIT_APK)
+ .run();
+ assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+
+ verifyInstalledFiles(
+ INSTALLED_BASE_APK,
+ INSTALLED_BASE_APK_FSV_SIG,
+ INSTALLED_SPLIT_APK,
+ INSTALLED_SPLIT_APK_FSV_SIG);
+ verifyInstalledFilesHaveFsverity();
+ }
+
+ @Test
+ public void testInstallBaseWithDm() throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFileAndSignature(BASE_APK)
+ .addFileAndSignature(BASE_APK_DM)
+ .run();
+ assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+
+ verifyInstalledFiles(
+ INSTALLED_BASE_APK,
+ INSTALLED_BASE_APK_FSV_SIG,
+ INSTALLED_BASE_DM,
+ INSTALLED_BASE_DM_FSV_SIG);
+ verifyInstalledFilesHaveFsverity();
+ }
+
+ @Test
+ public void testInstallEverything() throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFileAndSignature(BASE_APK)
+ .addFileAndSignature(BASE_APK_DM)
+ .addFileAndSignature(SPLIT_APK)
+ .addFileAndSignature(SPLIT_APK_DM)
+ .run();
+ assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+
+ verifyInstalledFiles(
+ INSTALLED_BASE_APK,
+ INSTALLED_BASE_APK_FSV_SIG,
+ INSTALLED_BASE_DM,
+ INSTALLED_BASE_DM_FSV_SIG,
+ INSTALLED_SPLIT_APK,
+ INSTALLED_SPLIT_APK_FSV_SIG,
+ INSTALLED_SPLIT_DM,
+ INSTALLED_SPLIT_DM_FSV_SIG);
+ verifyInstalledFilesHaveFsverity();
+ }
+
+ @Test
+ public void testInstallSplitOnly()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFileAndSignature(BASE_APK)
+ .run();
+ assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+ verifyInstalledFiles(
+ INSTALLED_BASE_APK,
+ INSTALLED_BASE_APK_FSV_SIG);
+
+ new InstallMultiple()
+ .inheritFrom(TARGET_PACKAGE)
+ .addFileAndSignature(SPLIT_APK)
+ .run();
+
+ verifyInstalledFiles(
+ INSTALLED_BASE_APK,
+ INSTALLED_BASE_APK_FSV_SIG,
+ INSTALLED_SPLIT_APK,
+ INSTALLED_SPLIT_APK_FSV_SIG);
+ verifyInstalledFilesHaveFsverity();
+ }
+
+ @Test
+ public void testInstallSplitOnlyMissingSignature()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFileAndSignature(BASE_APK)
+ .run();
+ assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+ verifyInstalledFiles(
+ INSTALLED_BASE_APK,
+ INSTALLED_BASE_APK_FSV_SIG);
+
+ new InstallMultiple()
+ .inheritFrom(TARGET_PACKAGE)
+ .addFile(SPLIT_APK)
+ .runExpectingFailure();
+ }
+
+ @Test
+ public void testInstallSplitOnlyWithoutBaseSignature()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFile(BASE_APK)
+ .run();
+ assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+ verifyInstalledFiles(INSTALLED_BASE_APK);
+
+ new InstallMultiple()
+ .inheritFrom(TARGET_PACKAGE)
+ .addFileAndSignature(SPLIT_APK)
+ .run();
+ verifyInstalledFiles(
+ INSTALLED_BASE_APK,
+ INSTALLED_SPLIT_APK,
+ INSTALLED_SPLIT_APK_FSV_SIG);
+
+ }
+
+ @Test
+ public void testInstallOnlyBaseHasFsvSig()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFileAndSignature(BASE_APK)
+ .addFile(BASE_APK_DM)
+ .addFile(SPLIT_APK)
+ .addFile(SPLIT_APK_DM)
+ .runExpectingFailure();
+ }
+
+ @Test
+ public void testInstallOnlyDmHasFsvSig()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFile(BASE_APK)
+ .addFileAndSignature(BASE_APK_DM)
+ .addFile(SPLIT_APK)
+ .addFile(SPLIT_APK_DM)
+ .runExpectingFailure();
+ }
+
+ @Test
+ public void testInstallOnlySplitHasFsvSig()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFile(BASE_APK)
+ .addFile(BASE_APK_DM)
+ .addFileAndSignature(SPLIT_APK)
+ .addFile(SPLIT_APK_DM)
+ .runExpectingFailure();
+ }
+
+ @Test
+ public void testInstallBaseWithFsvSigThenSplitWithout()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFileAndSignature(BASE_APK)
+ .run();
+ assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+ verifyInstalledFiles(
+ INSTALLED_BASE_APK,
+ INSTALLED_BASE_APK_FSV_SIG);
+
+ new InstallMultiple()
+ .addFile(SPLIT_APK)
+ .runExpectingFailure();
+ }
+
+ @Test
+ public void testInstallBaseWithoutFsvSigThenSplitWith()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple()
+ .addFile(BASE_APK)
+ .run();
+ assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+ verifyInstalledFiles(INSTALLED_BASE_APK);
+
+ new InstallMultiple()
+ .addFileAndSignature(SPLIT_APK)
+ .runExpectingFailure();
+ }
+
+ @Test
+ public void testFsverityFileIsImmutableAndReadable() throws DeviceNotAvailableException {
+ new InstallMultiple().addFileAndSignature(BASE_APK).run();
+ String apkPath = getApkPath(TARGET_PACKAGE);
+
+ assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+ expectRemoteCommandToFail("echo -n '' >> " + apkPath);
+ expectRemoteCommandToSucceed("cat " + apkPath + " > /dev/null");
+ }
+
+ @Test
+ public void testFsverityFailToReadModifiedBlockAtFront() throws DeviceNotAvailableException {
+ new InstallMultiple().addFileAndSignature(BASE_APK).run();
+ String apkPath = getApkPath(TARGET_PACKAGE);
+
+ long apkSize = getFileSizeInBytes(apkPath);
+ long offsetFirstByte = 0;
+
+ // The first two pages should be both readable at first.
+ assertTrue(canReadByte(apkPath, offsetFirstByte));
+ if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
+ assertTrue(canReadByte(apkPath, offsetFirstByte + FSVERITY_PAGE_SIZE));
+ }
+
+ // Damage the file directly against the block device.
+ damageFileAgainstBlockDevice(apkPath, offsetFirstByte);
+
+ // Expect actual read from disk to fail but only at damaged page.
+ dropCaches();
+ assertFalse(canReadByte(apkPath, offsetFirstByte));
+ if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
+ long lastByteOfTheSamePage =
+ offsetFirstByte % FSVERITY_PAGE_SIZE + FSVERITY_PAGE_SIZE - 1;
+ assertFalse(canReadByte(apkPath, lastByteOfTheSamePage));
+ assertTrue(canReadByte(apkPath, lastByteOfTheSamePage + 1));
+ }
+ }
+
+ @Test
+ public void testFsverityFailToReadModifiedBlockAtBack() throws DeviceNotAvailableException {
+ new InstallMultiple().addFileAndSignature(BASE_APK).run();
+ String apkPath = getApkPath(TARGET_PACKAGE);
+
+ long apkSize = getFileSizeInBytes(apkPath);
+ long offsetOfLastByte = apkSize - 1;
+
+ // The first two pages should be both readable at first.
+ assertTrue(canReadByte(apkPath, offsetOfLastByte));
+ if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
+ assertTrue(canReadByte(apkPath, offsetOfLastByte - FSVERITY_PAGE_SIZE));
+ }
+
+ // Damage the file directly against the block device.
+ damageFileAgainstBlockDevice(apkPath, offsetOfLastByte);
+
+ // Expect actual read from disk to fail but only at damaged page.
+ dropCaches();
+ assertFalse(canReadByte(apkPath, offsetOfLastByte));
+ if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
+ long firstByteOfTheSamePage = offsetOfLastByte - offsetOfLastByte % FSVERITY_PAGE_SIZE;
+ assertFalse(canReadByte(apkPath, firstByteOfTheSamePage));
+ assertTrue(canReadByte(apkPath, firstByteOfTheSamePage - 1));
+ }
+ }
+
+ private void verifyInstalledFilesHaveFsverity() throws DeviceNotAvailableException {
+ // Verify that all files are protected by fs-verity
+ String apkPath = getApkPath(TARGET_PACKAGE);
+ String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
+ long kTargetOffset = 0;
+ for (String basename : expectRemoteCommandToSucceed("ls " + appDir).split("\n")) {
+ if (basename.endsWith(".apk") || basename.endsWith(".dm")) {
+ String path = appDir + "/" + basename;
+ damageFileAgainstBlockDevice(path, kTargetOffset);
+
+ // Retry is sometimes needed to pass the test. Package manager may have FD leaks
+ // (see b/122744005 as example) that prevents the file in question to be evicted
+ // from filesystem cache. Forcing GC workarounds the problem.
+ int retry = 5;
+ for (; retry > 0; retry--) {
+ dropCaches();
+ if (!canReadByte(path, kTargetOffset)) {
+ break;
+ }
+ try {
+ Thread.sleep(1000);
+ String pid = expectRemoteCommandToSucceed("pidof system_server");
+ mDevice.executeShellV2Command("kill -10 " + pid); // force GC
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return;
+ }
+ }
+ assertTrue("Read from " + path + " should fail", retry > 0);
+ }
+ }
+ }
+
+ private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException {
+ String apkPath = getApkPath(TARGET_PACKAGE);
+ String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
+ HashSet<String> actualFiles = new HashSet<>(Arrays.asList(
+ expectRemoteCommandToSucceed("ls " + appDir).split("\n")));
+ assertTrue(actualFiles.remove("lib"));
+ assertTrue(actualFiles.remove("oat"));
+
+ HashSet<String> expectedFiles = new HashSet<>(Arrays.asList(filenames));
+ assertEquals(expectedFiles, actualFiles);
+ }
+
+ private void damageFileAgainstBlockDevice(String path, long offsetOfTargetingByte)
+ throws DeviceNotAvailableException {
+ assertTrue(path.startsWith("/data/"));
+ ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data");
+ expectRemoteCommandToSucceed(String.join(" ", DAMAGING_EXECUTABLE,
+ mountPoint.filesystem, path, Long.toString(offsetOfTargetingByte)));
+ }
+
+ private String getApkPath(String packageName) throws DeviceNotAvailableException {
+ String line = expectRemoteCommandToSucceed("pm path " + packageName + " | grep base.apk");
+ int index = line.trim().indexOf(":");
+ assertTrue(index >= 0);
+ return line.substring(index + 1);
+ }
+
+ private long getFileSizeInBytes(String packageName) throws DeviceNotAvailableException {
+ return Long.parseLong(expectRemoteCommandToSucceed("stat -c '%s' " + packageName).trim());
+ }
+
+ private void dropCaches() throws DeviceNotAvailableException {
+ expectRemoteCommandToSucceed("sync && echo 1 > /proc/sys/vm/drop_caches");
+ }
+
+ private boolean canReadByte(String filePath, long offset) throws DeviceNotAvailableException {
+ CommandResult result = mDevice.executeShellV2Command(
+ "dd if=" + filePath + " bs=1 count=1 skip=" + Long.toString(offset));
+ return result.getStatus() == CommandStatus.SUCCESS;
+ }
+
+ private String expectRemoteCommandToSucceed(String cmd) throws DeviceNotAvailableException {
+ CommandResult result = mDevice.executeShellV2Command(cmd);
+ assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS,
+ result.getStatus());
+ return result.getStdout();
+ }
+
+ private void expectRemoteCommandToFail(String cmd) throws DeviceNotAvailableException {
+ CommandResult result = mDevice.executeShellV2Command(cmd);
+ assertTrue("Unexpected success from `" + cmd + "`: " + result.getStderr(),
+ result.getStatus() != CommandStatus.SUCCESS);
+ }
+
+ private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
+ InstallMultiple() {
+ super(getDevice(), getBuild());
+ }
+
+ InstallMultiple addFileAndSignature(String filename) {
+ try {
+ addFile(filename);
+ addFile(filename + ".fsv_sig");
+ } catch (FileNotFoundException e) {
+ fail("Missing test file: " + e);
+ }
+ return this;
+ }
+ }
+}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java b/tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java
new file mode 100644
index 000000000000..02e73d157dde
--- /dev/null
+++ b/tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java
@@ -0,0 +1,140 @@
+/*
+ * 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 com.android.apkverity;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base class for invoking the install-multiple command via ADB. Subclass this for less typing:
+ *
+ * <code> private class InstallMultiple extends BaseInstallMultiple&lt;InstallMultiple&gt; { public
+ * InstallMultiple() { super(getDevice(), null); } } </code>
+ */
+/*package*/ class BaseInstallMultiple<T extends BaseInstallMultiple<?>> {
+
+ private final ITestDevice mDevice;
+ private final IBuildInfo mBuild;
+
+ private final List<String> mArgs = new ArrayList<>();
+ private final Map<File, String> mFileToRemoteMap = new HashMap<>();
+
+ /*package*/ BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo) {
+ mDevice = device;
+ mBuild = buildInfo;
+ addArg("-g");
+ }
+
+ T addArg(String arg) {
+ mArgs.add(arg);
+ return (T) this;
+ }
+
+ T addFile(String filename) throws FileNotFoundException {
+ return addFile(filename, filename);
+ }
+
+ T addFile(String filename, String remoteName) throws FileNotFoundException {
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
+ mFileToRemoteMap.put(buildHelper.getTestFile(filename), remoteName);
+ return (T) this;
+ }
+
+ T inheritFrom(String packageName) {
+ addArg("-r");
+ addArg("-p " + packageName);
+ return (T) this;
+ }
+
+ void run() throws DeviceNotAvailableException {
+ run(true);
+ }
+
+ void runExpectingFailure() throws DeviceNotAvailableException {
+ run(false);
+ }
+
+ private void run(boolean expectingSuccess) throws DeviceNotAvailableException {
+ final ITestDevice device = mDevice;
+
+ // Create an install session
+ final StringBuilder cmd = new StringBuilder();
+ cmd.append("pm install-create");
+ for (String arg : mArgs) {
+ cmd.append(' ').append(arg);
+ }
+
+ String result = device.executeShellCommand(cmd.toString());
+ TestCase.assertTrue(result, result.startsWith("Success"));
+
+ final int start = result.lastIndexOf("[");
+ final int end = result.lastIndexOf("]");
+ int sessionId = -1;
+ try {
+ if (start != -1 && end != -1 && start < end) {
+ sessionId = Integer.parseInt(result.substring(start + 1, end));
+ }
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException("Failed to parse install session: " + result);
+ }
+ if (sessionId == -1) {
+ throw new IllegalStateException("Failed to create install session: " + result);
+ }
+
+ // Push our files into session. Ideally we'd use stdin streaming,
+ // but ddmlib doesn't support it yet.
+ for (final Map.Entry<File, String> entry : mFileToRemoteMap.entrySet()) {
+ final File file = entry.getKey();
+ final String remoteName = entry.getValue();
+ final String remotePath = "/data/local/tmp/" + file.getName();
+ if (!device.pushFile(file, remotePath)) {
+ throw new IllegalStateException("Failed to push " + file);
+ }
+
+ cmd.setLength(0);
+ cmd.append("pm install-write");
+ cmd.append(' ').append(sessionId);
+ cmd.append(' ').append(remoteName);
+ cmd.append(' ').append(remotePath);
+
+ result = device.executeShellCommand(cmd.toString());
+ TestCase.assertTrue(result, result.startsWith("Success"));
+ }
+
+ // Everything staged; let's pull trigger
+ cmd.setLength(0);
+ cmd.append("pm install-commit");
+ cmd.append(' ').append(sessionId);
+
+ result = device.executeShellCommand(cmd.toString());
+ if (expectingSuccess) {
+ TestCase.assertTrue(result, result.contains("Success"));
+ } else {
+ TestCase.assertFalse(result, result.contains("Success"));
+ }
+ }
+}
diff --git a/tests/ApkVerityTest/testdata/Android.bp b/tests/ApkVerityTest/testdata/Android.bp
new file mode 100644
index 000000000000..c10b0cef21d7
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/Android.bp
@@ -0,0 +1,77 @@
+// 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.
+
+filegroup {
+ name: "ApkVerityTestKeyPem",
+ srcs: ["ApkVerityTestKey.pem"],
+}
+
+filegroup {
+ name: "ApkVerityTestCertPem",
+ srcs: ["ApkVerityTestCert.pem"],
+}
+
+filegroup {
+ name: "ApkVerityTestCertDer",
+ srcs: ["ApkVerityTestCert.der"],
+}
+
+filegroup {
+ name: "ApkVerityTestAppDm",
+ srcs: ["ApkVerityTestApp.dm"],
+}
+
+filegroup {
+ name: "ApkVerityTestAppSplitDm",
+ srcs: ["ApkVerityTestAppSplit.dm"],
+}
+
+genrule_defaults {
+ name: "apk_verity_sig_gen_default",
+ tools: ["fsverity"],
+ tool_files: [":ApkVerityTestKeyPem", ":ApkVerityTestCertPem"],
+ cmd: "$(location fsverity) sign $(in) $(out) " +
+ "--key=$(location :ApkVerityTestKeyPem) " +
+ "--cert=$(location :ApkVerityTestCertPem) " +
+ "> /dev/null",
+}
+
+genrule {
+ name: "ApkVerityTestAppFsvSig",
+ defaults: ["apk_verity_sig_gen_default"],
+ srcs: [":ApkVerityTestApp"],
+ out: ["ApkVerityTestApp.apk.fsv_sig"],
+}
+
+genrule {
+ name: "ApkVerityTestAppDmFsvSig",
+ defaults: ["apk_verity_sig_gen_default"],
+ srcs: [":ApkVerityTestAppDm"],
+ out: ["ApkVerityTestApp.dm.fsv_sig"],
+}
+
+genrule {
+ name: "ApkVerityTestAppSplitFsvSig",
+ defaults: ["apk_verity_sig_gen_default"],
+ srcs: [":ApkVerityTestAppSplit"],
+ out: ["ApkVerityTestAppSplit.apk.fsv_sig"],
+}
+
+genrule {
+ name: "ApkVerityTestAppSplitDmFsvSig",
+ defaults: ["apk_verity_sig_gen_default"],
+ srcs: [":ApkVerityTestAppSplitDm"],
+ out: ["ApkVerityTestAppSplit.dm.fsv_sig"],
+}
+
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestApp.dm b/tests/ApkVerityTest/testdata/ApkVerityTestApp.dm
new file mode 100644
index 000000000000..e53a86131366
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/ApkVerityTestApp.dm
Binary files differ
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dm b/tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dm
new file mode 100644
index 000000000000..75396f1ba730
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dm
Binary files differ
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestCert.der b/tests/ApkVerityTest/testdata/ApkVerityTestCert.der
new file mode 100644
index 000000000000..fe9029b53aa1
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/ApkVerityTestCert.der
Binary files differ
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestCert.pem b/tests/ApkVerityTest/testdata/ApkVerityTestCert.pem
new file mode 100644
index 000000000000..6c0b7b1f635a
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/ApkVerityTestCert.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFLjCCAxagAwIBAgIJAKZbtMlZZwtdMA0GCSqGSIb3DQEBCwUAMCwxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTEQMA4GA1UECgwHQW5kcm9pZDAeFw0xODEyMTky
+MTA5MzVaFw0xOTAxMTgyMTA5MzVaMCwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
+QTEQMA4GA1UECgwHQW5kcm9pZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
+ggIBAKnrw4WiFgFBq6vXqcLc97iwvcYPZmeIjQqYRF+CHwXBXx8IyDlMfPrgyIYo
+ZLkosnUK/Exuypdu6UEtdqtYPknC6w9z4YkxqsKtyxyB1b13ptcTHh3bf2N8bqGr
+8gWWLxj0QjumCtFi7Z/TCwB5t3b3gtC+0jVfABSWrm5PNkgk7jIP+4KeYLDCDfiJ
+XH3uHu6OASiSHTOnrmLWSaSw0y6G4OFthHqQnMywasly0r6m+Mif+K0ZUV7hBRi/
+SfqcJ1HTCXTJMskEyV6Qx2sHF/VbK2gdUv56z6OVRNSs/FxPBiWVMuZZKh1FpBVI
+gbGxusf2Awwtc+Soxr4/P1YFcrwfA/ff9FK3Yg/Cd3ZMGbzUkbEMEkE5BW7Gbjmx
+wz3mYTiRfa2L/Bl4MiMqNi0tfORLkmg+V/EItzfhZ/HsXMOCBsnuj4KnFslmbamz
+t9opypj2JLGk+lXhZ5gFNFw8tYH1AnG1AIXe5u+6Fq2nQ1y/ncGUTR5Sw4de/Gee
+C0UgR+KiFEdKupMKbXgSKl+0QPz/i2eSpcDOKMwZ4WiNrkbccbCyr38so+j5DfWF
+IeZA9a/IlysA6G8yU2TfXBc65VCIEQRJOQdUOZFDO8OSoqGP+fbA6edpmovGw+TH
+sM/NkmpEXpQm7BVOI4oVjdf4pKPp0zaW2YcaA3xU2w6eF17pAgMBAAGjUzBRMB0G
+A1UdDgQWBBRGpHYy7yiLEYalGuF1va6zJKGD/zAfBgNVHSMEGDAWgBRGpHYy7yiL
+EYalGuF1va6zJKGD/zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IC
+AQAao6ZBM122F0pYb2QLahIyyGEr3LfSdBGID4068pVik4ncIefFz36Xf9AFxRQd
+KHmwRYNPHiLRIEGdtqplC5pZDeHz41txIArNIZKzDWOYtdcFyCz8umuj912BmsoM
+YUQhT6F1sX53SWcKxEP/aJ2kltSlPFX99e3Vx9eRkceV1oe2NM6ZG8hnYCfCAMeJ
+jRTpbqCGaAsEHFtIx6wt3zEtUXIVg4aYFQs/qjTjeP8ByIj0b4lZrceEoTeRimuj
++4aAI+jBxLkwaN3hseQHzRNpgPehIVV/0RU92yzOD/WN4YwE6rwjKEI1lihHNBDa
++DwGtGbHmIUzjW1qArig+mzUIhfYIJAxrx20ynPz/Q+C7+iXhTDAYQlxTle0pX8m
+yM2DUdPo97eLOzQ4JDHxtcN3ntTEJKKvrmzKvWuxy/yoLwS7MtLH6RETTHabH3Qd
+CP83X7z8zTyxgPxHdfHo9sgR/4C9RHGJx4OpBTQaiqfjSpDqJSIQdbrHGOQDgYwL
+KQyiQuhukmNgRCB6dJoZJ/MyaNuMsXV9QobsDHW1oSuCvPAihVoWHJxt8m4Ma0jJ
+EIbEPT2Umw1F/P+CeXnVQwhPvzQKHCa+6cC/YdjTqIKLmQV8X3HUBUIMhP2JGDic
+MnUipTm/RwWZVOjCJaFqk5sVq3L0Lyd0XVUWSK1a4IcrsA==
+-----END CERTIFICATE-----
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestKey.pem b/tests/ApkVerityTest/testdata/ApkVerityTestKey.pem
new file mode 100644
index 000000000000..f0746c162421
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/ApkVerityTestKey.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCp68OFohYBQaur
+16nC3Pe4sL3GD2ZniI0KmERfgh8FwV8fCMg5THz64MiGKGS5KLJ1CvxMbsqXbulB
+LXarWD5JwusPc+GJMarCrcscgdW9d6bXEx4d239jfG6hq/IFli8Y9EI7pgrRYu2f
+0wsAebd294LQvtI1XwAUlq5uTzZIJO4yD/uCnmCwwg34iVx97h7ujgEokh0zp65i
+1kmksNMuhuDhbYR6kJzMsGrJctK+pvjIn/itGVFe4QUYv0n6nCdR0wl0yTLJBMle
+kMdrBxf1WytoHVL+es+jlUTUrPxcTwYllTLmWSodRaQVSIGxsbrH9gMMLXPkqMa+
+Pz9WBXK8HwP33/RSt2IPwnd2TBm81JGxDBJBOQVuxm45scM95mE4kX2ti/wZeDIj
+KjYtLXzkS5JoPlfxCLc34Wfx7FzDggbJ7o+CpxbJZm2ps7faKcqY9iSxpPpV4WeY
+BTRcPLWB9QJxtQCF3ubvuhatp0Ncv53BlE0eUsOHXvxnngtFIEfiohRHSrqTCm14
+EipftED8/4tnkqXAzijMGeFoja5G3HGwsq9/LKPo+Q31hSHmQPWvyJcrAOhvMlNk
+31wXOuVQiBEESTkHVDmRQzvDkqKhj/n2wOnnaZqLxsPkx7DPzZJqRF6UJuwVTiOK
+FY3X+KSj6dM2ltmHGgN8VNsOnhde6QIDAQABAoICAGT21tWnisWyXKwd2BwWKgeO
+1SRDcEiihZO/CBlr+rzzum55TGdngHedauj0RW0Ttn3/SgysZCp415ZHylRjeZdg
+f0VOSLu5TEqi86X7q6IJ35O6I1IAY4AcpqvfvE3/f/qm4FgLADCMRL+LqeTdbdr9
+lLguOj9GNIkHQ5v96zYQ44vRnVNugetlUuHT1KZq/+wlaqDNuRZBU0gdJeL6wnDJ
+6gNojKg7F0A0ry8F0B1Cn16uVxebjJMAx4N93hpQALkI2XyQNGHnOzO6eROqQl0i
+j/csPW1CUfBUOHLaWpUKy483SOhAINsFz0pqK84G2gIItqTcuRksA/N1J1AYqqQO
++/8IK5Mb9j0RaYYrBG83luGCWYauAsWg2Yol6fUGju8IY/zavOaES42XogY588Ad
+JzW+njjxXcnoD/u5keWrGwbPdGfoaLLg4eMlRBT4yNicyT04knXjFG4QTfLY5lF/
+VKdvZk6RMoCLdAtgN6EKHtcwuoYR967otsbavshngZ9HE/ic5/TdNFCBjxs6q9bm
+docC4CLHU/feXvOCYSnIfUpDzEPV96Gbk6o0qeYn3RUSGzRpXQHxXXfszEESUWnd
+2rtfXxqA7C5n8CshBfKJND7/LKRGpBRaYWJtc4hFmo8prhXfOb40PEZNlx8mcsEz
+WYZpmvFQHU8+bZIm0a5RAoIBAQDaCAje9xLKN1CYzygA/U3x2CsGuWWyh9xM1oR5
+5t+nn0EeIMrzGuHrD4hdbZiTiJcO5dpSg/3dssc/QLJEdv+BoMEgSYTf3TX03dIb
+kSlj+ONobejO4nVoUP0axTvVe/nuMYvLguTM6OCFvgV752TFxVyVHl6RM+rQYGCl
+ajbBCsCRg4QgpZ/RHWf+3KMJunzwWBlsAXcjOudneYqEl713h/q1lc5cONIglQDU
+E+bc5q6q++c/H8dYaWq4QE4CQU8wsq77/bZk8z1jheOV0HkwaH5ShtKD7bk/4MA9
+jWQUDW6/LRXkNiPExsAZxnP3mxhtUToWq1nedF6kPmNBko+9AoIBAQDHgvAql6i7
+osTYUcY/GldPmnrvfqbKmD0nI8mnaJfN2vGwjB/ol3lm+pviKIQ4ER80xsdn4xK0
+2cC9OdkY2UX7zilKohxvKVsbVOYpUwfpoRQO1Euddb6iAMqgGDyDzRBDTzTx5pB5
+XL9B/XuJVCMkjsNdD9iEbjdY1Epv7kYf53zfvrXdqv24uSNAszPFBLLPHSC9yONE
+a/t3mHGZ2cjr52leGNGY7ib6GNGBUeA34SM9g97tU9pAgy712RfZhH6fA93CLk6T
+DKoch56YId71vZt2J0Lrk4TWnnpidSoRmzKfVIJwjCmgYbI+2eDp7h0Z0DnDbji6
+9BPt3RWsoZidAoIBAA2A7+O3U7+Ye3JraiPdjGVNKSUKeIT9KyTLKHtQVEvSbjsK
+dudlo9ZmKOD4d7mzfP+cNtBjgmanuvVs8V2SLTL/HNb+Fq+yyLO4xVmVvQWHFbaT
+EBc4KWNjmLl+u7z2J72b7feVzMvwJG/EHBzXcQNavOgzcFH38DQls/aqxGdiXhjl
+F1raRzKxao57ZdGlbjWIj1KEKLfS3yAmg/DAYSi1EE8MzzIhBsqjz+BStzq5Qtou
+Ld1X/4W3SbfNq8cx+lCe0H2k8hYAhq3STg0qU0cvQZuk5Abtw0p0hhOJ3UfsqQ5I
+IZH31HFMiftOskIEphenLzzWMgO4G2B6yLT3+dUCggEAOLF1i7Ti5sbfBtVd70qN
+6vnr2yhzPvi5z+h0ghTPpliD+3YmDxMUFXY7W63FvKTo6DdgLJ4zD58dDOhmT5BW
+ObKguyuLxu7Ki965NJ76jaIPMBOVlR4DWMe+zHV2pMFd0LKuSdsJzOLVGmxscV6u
+SdIjo8s/7InhQmW47UuZM7G1I2NvDJltVdOON/F0UZT/NqmBR0zRf/zrTVXNWjmv
+xZFRuMJ2tO1fuAvbZNMeUuKv/+f8LhZ424IrkwLoqw/iZ09S8b306AZeRJMpNvPR
+BqWlipKnioe15MLN5jKDDNO8M9hw5Ih/v6pjW0bQicj3DgHEmEs25bE8BIihgxe8
+ZQKCAQEAsWKsUv13OEbYYAoJgbzDesWF9NzamFB0NLyno9SChvFPH/d8RmAuti7Y
+BQUoBswLK24DF/TKf1YocsZq8tu+pnv0Nx1wtK4K+J3A1BYDm7ElpO3Km+HPUBtf
+C9KGT5hotlMQVTpYSDG/QeWbfl4UnNZcbg8pmv38NwV1eDoVDfaVrRYJzQn75+Tf
+s/WUq1x5PElR/4pNIU2i6pJGd6FimhRweJu/INR36spWmbMRNX8fyXx+9EBqMbVp
+vS2xGgxxQT6bAvBfRlpgi87T9v5Gqoy6/jM/wX9smH9PfUV1vK32n3Zrbd46gwZW
+p2aUlQOLXU9SjQTirZbdCZP0XHtFsg==
+-----END PRIVATE KEY-----
diff --git a/tests/ApkVerityTest/testdata/README.md b/tests/ApkVerityTest/testdata/README.md
new file mode 100644
index 000000000000..163cb183a5ad
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/README.md
@@ -0,0 +1,13 @@
+This test only runs on rooted / debuggable device.
+
+The test tries to install subsets of base.{apk,dm}, split.{apk,dm} and their
+corresponding .fsv_sig files (generated by build rule). If installed, the
+tests also tries to tamper with the file at absolute disk offset to verify
+if fs-verity is effective.
+
+How to generate dex metadata (.dm)
+==================================
+
+ adb shell profman --generate-test-profile=/data/local/tmp/primary.prof
+ adb pull /data/local/tmp/primary.prof
+ zip foo.dm primary.prof
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml b/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml
index aec9f77cf922..7291dc729541 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml
+++ b/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml
@@ -28,6 +28,8 @@
<uses-permission android:name="android.permission.SET_TIME" />
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
index 7d826f7172da..4cd56c3c42be 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
+++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
@@ -22,6 +22,7 @@ import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.SystemProperties;
import android.os.storage.StorageManager;
+import android.provider.DeviceConfig;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -30,7 +31,9 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -52,6 +55,13 @@ import java.util.concurrent.TimeUnit;
* 3. Under low storage conditions and package is recently used, check
* that dexopt upgrades test app to $(getprop pm.dexopt.bg-dexopt).
*
+ * When downgrade feature is on (downgrade_unused_apps_enabled flag is set to true):
+ * 4 On low storage, check that the inactive packages are downgraded.
+ * 5. On low storage, check that used packages are upgraded.
+ * 6. On storage completely full, dexopt fails.
+ * 7. Not on low storage, unused packages are upgraded.
+ * 8. Low storage, unused app is downgraded. When app is used again, app is upgraded.
+ *
* Each test case runs "cmd package bg-dexopt-job com.android.frameworks.bgdexopttest".
*
* The setup for these tests make sure this package has been configured to have been recently used
@@ -59,6 +69,10 @@ import java.util.concurrent.TimeUnit;
* recently used, it sets the time forward more than
* `getprop pm.dexopt.downgrade_after_inactive_days` days.
*
+ * For some of the tests, the DeviceConfig flags inactive_app_threshold_days and
+ * downgrade_unused_apps_enabled are set. These turn on/off the downgrade unused apps feature for
+ * all devices and set the time threshold for unused apps.
+ *
* For tests that require low storage, the phone is filled up.
*
* Run with "atest BackgroundDexOptServiceIntegrationTests".
@@ -80,10 +94,14 @@ public final class BackgroundDexOptServiceIntegrationTests {
"pm.dexopt.downgrade_after_inactive_days", 0);
// Needs to be between 1.0 and 2.0.
private static final double LOW_STORAGE_MULTIPLIER = 1.5;
+ private static final int DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS = 15;
// The file used to fill up storage.
private File mBigFile;
+ @Rule
+ public ExpectedException mExpectedException = ExpectedException.none();
+
// Remember start time.
@BeforeClass
public static void setUpAll() {
@@ -196,11 +214,27 @@ public final class BackgroundDexOptServiceIntegrationTests {
logSpaceRemaining();
}
+ private void fillUpStorageCompletely() throws IOException {
+ fillUpStorage((getStorageLowBytes()));
+ }
+
// Fill up storage so that device is in low storage condition.
private void fillUpToLowStorage() throws IOException {
fillUpStorage((long) (getStorageLowBytes() * LOW_STORAGE_MULTIPLIER));
}
+ private void setInactivePackageThreshold(int threshold) {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ "inactive_app_threshold_days", Integer.toString(threshold), false);
+ }
+
+ private void enableDowngradeFeature(boolean enabled) {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ "downgrade_unused_apps_enabled", Boolean.toString(enabled), false);
+ }
+
// TODO(aeubanks): figure out how to get scheduled bg-dexopt to run
private static void runBackgroundDexOpt() throws IOException {
String result = runShellCommand("cmd package bg-dexopt-job " + PACKAGE_NAME);
@@ -244,7 +278,7 @@ public final class BackgroundDexOptServiceIntegrationTests {
// Test that background dexopt under normal conditions succeeds.
@Test
- public void testBackgroundDexOpt() throws IOException {
+ public void testBackgroundDexOpt_normalConditions_dexOptSucceeds() throws IOException {
// Set filter to quicken.
compilePackageWithFilter(PACKAGE_NAME, "verify");
Assert.assertEquals("verify", getCompilerFilter(PACKAGE_NAME));
@@ -257,17 +291,16 @@ public final class BackgroundDexOptServiceIntegrationTests {
// Test that background dexopt under low storage conditions upgrades used packages.
@Test
- public void testBackgroundDexOptDowngradeSkipRecentlyUsedPackage() throws IOException {
+ public void testBackgroundDexOpt_lowStorage_usedPkgsUpgraded() throws IOException {
// Should be less than DOWNGRADE_AFTER_DAYS.
long deltaDays = DOWNGRADE_AFTER_DAYS - 1;
try {
+ enableDowngradeFeature(false);
// Set time to future.
setTimeFutureDays(deltaDays);
-
// Set filter to quicken.
compilePackageWithFilter(PACKAGE_NAME, "quicken");
Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
-
// Fill up storage to trigger low storage threshold.
fillUpToLowStorage();
@@ -282,18 +315,48 @@ public final class BackgroundDexOptServiceIntegrationTests {
}
// Test that background dexopt under low storage conditions downgrades unused packages.
+ // This happens if the system property pm.dexopt.downgrade_after_inactive_days is set
+ // (e.g. on Android Go devices).
@Test
- public void testBackgroundDexOptDowngradeSuccessful() throws IOException {
+ public void testBackgroundDexOpt_lowStorage_unusedPkgsDowngraded()
+ throws IOException {
// Should be more than DOWNGRADE_AFTER_DAYS.
long deltaDays = DOWNGRADE_AFTER_DAYS + 1;
try {
+ enableDowngradeFeature(false);
// Set time to future.
setTimeFutureDays(deltaDays);
-
// Set filter to quicken.
compilePackageWithFilter(PACKAGE_NAME, "quicken");
Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
+ // Fill up storage to trigger low storage threshold.
+ fillUpToLowStorage();
+
+ runBackgroundDexOpt();
+
+ // Verify that downgrade is successful.
+ Assert.assertEquals(DOWNGRADE_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+ } finally {
+ // Reset time.
+ setTimeFutureDays(-deltaDays);
+ }
+ }
+ // Test that the background dexopt downgrades inactive packages when the downgrade feature is
+ // enabled.
+ @Test
+ public void testBackgroundDexOpt_downgradeFeatureEnabled_lowStorage_inactivePkgsDowngraded()
+ throws IOException {
+ // Should be more than DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS.
+ long deltaDays = DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS + 1;
+ try {
+ enableDowngradeFeature(true);
+ setInactivePackageThreshold(DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS);
+ // Set time to future.
+ setTimeFutureDays(deltaDays);
+ // Set filter to quicken.
+ compilePackageWithFilter(PACKAGE_NAME, "quicken");
+ Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
// Fill up storage to trigger low storage threshold.
fillUpToLowStorage();
@@ -307,4 +370,106 @@ public final class BackgroundDexOptServiceIntegrationTests {
}
}
+ // Test that the background dexopt upgrades used packages when the downgrade feature is enabled.
+ // This test doesn't fill the device storage completely, but to a multiplier of the low storage
+ // threshold and this is why apps can still be optimized.
+ @Test
+ public void testBackgroundDexOpt_downgradeFeatureEnabled_lowStorage_usedPkgsUpgraded()
+ throws IOException {
+ enableDowngradeFeature(true);
+ // Set filter to quicken.
+ compilePackageWithFilter(PACKAGE_NAME, "quicken");
+ Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
+ // Fill up storage to trigger low storage threshold.
+ fillUpToLowStorage();
+
+ runBackgroundDexOpt();
+
+ /// Verify that bg-dexopt is successful in upgrading the used packages.
+ Assert.assertEquals(BG_DEXOPT_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+ }
+
+ // Test that the background dexopt fails and doesn't change the compilation filter of used
+ // packages when the downgrade feature is enabled and the storage is filled up completely.
+ // The bg-dexopt shouldn't optimise nor downgrade these packages.
+ @Test
+ public void testBackgroundDexOpt_downgradeFeatureEnabled_fillUpStorageCompletely_dexOptFails()
+ throws IOException {
+ enableDowngradeFeature(true);
+ String previousCompilerFilter = getCompilerFilter(PACKAGE_NAME);
+
+ // Fill up storage completely, without using a multiplier for the low storage threshold.
+ fillUpStorageCompletely();
+
+ // When the bg dexopt runs with the storage filled up completely, it will fail.
+ mExpectedException.expect(IllegalStateException.class);
+ runBackgroundDexOpt();
+
+ /// Verify that bg-dexopt doesn't change the compilation filter of used apps.
+ Assert.assertEquals(previousCompilerFilter, getCompilerFilter(PACKAGE_NAME));
+ }
+
+ // Test that the background dexopt upgrades the unused packages when the downgrade feature is
+ // on if the device is not low on storage.
+ @Test
+ public void testBackgroundDexOpt_downgradeFeatureEnabled_notLowStorage_unusedPkgsUpgraded()
+ throws IOException {
+ // Should be more than DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS.
+ long deltaDays = DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS + 1;
+ try {
+ enableDowngradeFeature(true);
+ setInactivePackageThreshold(DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS);
+ // Set time to future.
+ setTimeFutureDays(deltaDays);
+ // Set filter to quicken.
+ compilePackageWithFilter(PACKAGE_NAME, "quicken");
+ Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
+
+ runBackgroundDexOpt();
+
+ // Verify that bg-dexopt is successful in upgrading the unused packages when the device
+ // is not low on storage.
+ Assert.assertEquals(BG_DEXOPT_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+ } finally {
+ // Reset time.
+ setTimeFutureDays(-deltaDays);
+ }
+ }
+
+ // Test that when an unused package (which was downgraded) is used again, it's re-optimized when
+ // bg-dexopt runs again.
+ @Test
+ public void testBackgroundDexOpt_downgradeFeatureEnabled_downgradedPkgsUpgradedAfterUse()
+ throws IOException {
+ // Should be more than DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS.
+ long deltaDays = DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS + 1;
+ try {
+ enableDowngradeFeature(true);
+ setInactivePackageThreshold(DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS);
+ // Set time to future.
+ setTimeFutureDays(deltaDays);
+ // Fill up storage to trigger low storage threshold.
+ fillUpToLowStorage();
+ // Set filter to quicken.
+ compilePackageWithFilter(PACKAGE_NAME, "quicken");
+ Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
+
+ runBackgroundDexOpt();
+
+ // Verify that downgrade is successful.
+ Assert.assertEquals(DOWNGRADE_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+
+ // Reset time.
+ setTimeFutureDays(-deltaDays);
+ deltaDays = 0;
+ runBackgroundDexOpt();
+
+ // Verify that bg-dexopt is successful in upgrading the unused packages that were used
+ // again.
+ Assert.assertEquals(BG_DEXOPT_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+ } finally {
+ // Reset time.
+ setTimeFutureDays(-deltaDays);
+ }
+ }
}
diff --git a/tests/Codegen/Android.bp b/tests/Codegen/Android.bp
new file mode 100644
index 000000000000..966c5602959c
--- /dev/null
+++ b/tests/Codegen/Android.bp
@@ -0,0 +1,25 @@
+android_test {
+ name: "CodegenTests",
+ srcs: [
+ "**/*.java",
+ ],
+
+ platform_apis: true,
+ test_suites: ["device-tests"],
+ certificate: "platform",
+
+ optimize: {
+ enabled: false,
+ },
+
+ plugins: [
+ "staledataclass-annotation-processor",
+ ],
+ static_libs: [
+ "junit",
+ "hamcrest",
+ "hamcrest-library",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ ],
+}
diff --git a/tests/Codegen/AndroidManifest.xml b/tests/Codegen/AndroidManifest.xml
new file mode 100644
index 000000000000..2f1855035cd8
--- /dev/null
+++ b/tests/Codegen/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2015 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.codegentest">
+
+ <application/>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.codegentest"
+ android:label="Codegen test" />
+</manifest>
diff --git a/tests/RollbackTest/TestApp/res_v1/values-anydpi/values.xml b/tests/Codegen/AndroidTest.xml
index 90d3da2565cc..4dbbc5556c64 100644
--- a/tests/RollbackTest/TestApp/res_v1/values-anydpi/values.xml
+++ b/tests/Codegen/AndroidTest.xml
@@ -13,7 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<configuration description="Runs Codegen Tests.">
-<resources>
- <integer name="split_version">1</integer>
-</resources>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.codegentest" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/tests/Codegen/OWNERS b/tests/Codegen/OWNERS
new file mode 100644
index 000000000000..da723b3b67da
--- /dev/null
+++ b/tests/Codegen/OWNERS
@@ -0,0 +1 @@
+eugenesusla@google.com \ No newline at end of file
diff --git a/tests/Codegen/runTest.sh b/tests/Codegen/runTest.sh
new file mode 100755
index 000000000000..614cbb7c9eb6
--- /dev/null
+++ b/tests/Codegen/runTest.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+
+if [[ "$0" = *"/Codegen/runTest.sh" ]]; then
+ #running in subshell - print code to eval and exit
+ echo "source $0"
+else
+ function header_and_eval() {
+ printf "\n[ $* ]\n" 1>&2
+ eval "$@"
+ return $?
+ }
+
+ header_and_eval m -j16 codegen_cli && \
+ header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java && \
+ header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java && \
+ cd $ANDROID_BUILD_TOP &&
+ header_and_eval mmma -j16 frameworks/base/tests/Codegen && \
+ header_and_eval adb install -r -t $ANDROID_PRODUCT_OUT/testcases/CodegenTests/arm64/CodegenTests.apk && \
+ # header_and_eval adb shell am set-debug-app -w com.android.codegentest && \
+ header_and_eval adb shell am instrument -w -e package com.android.codegentest com.android.codegentest/androidx.test.runner.AndroidJUnitRunner
+
+ exitCode=$?
+
+ # header_and_eval adb shell am clear-debug-app
+
+ return $exitCode
+fi \ No newline at end of file
diff --git a/tests/Codegen/src/com/android/codegentest/MyDateParcelling.java b/tests/Codegen/src/com/android/codegentest/MyDateParcelling.java
new file mode 100644
index 000000000000..4faeb8ee8893
--- /dev/null
+++ b/tests/Codegen/src/com/android/codegentest/MyDateParcelling.java
@@ -0,0 +1,51 @@
+/*
+ * 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 com.android.codegentest;
+
+import android.os.Parcel;
+
+import com.android.internal.util.Parcelling;
+
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Sample {@link Parcelling} implementation for {@link Date}.
+ *
+ * See {@link SampleDataClass#mDate} for usage.
+ * See {@link SampleDataClass#writeToParcel} + {@link SampleDataClass#sParcellingForDate}
+ * for resulting generated code.
+ *
+ * Ignore {@link #sInstanceCount} - used for testing.
+ */
+public class MyDateParcelling implements Parcelling<Date> {
+
+ static AtomicInteger sInstanceCount = new AtomicInteger(0);
+
+ public MyDateParcelling() {
+ sInstanceCount.getAndIncrement();
+ }
+
+ @Override
+ public void parcel(Date item, Parcel dest, int parcelFlags) {
+ dest.writeLong(item.getTime());
+ }
+
+ @Override
+ public Date unparcel(Parcel source) {
+ return new Date(source.readLong());
+ }
+}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.aidl b/tests/Codegen/src/com/android/codegentest/SampleDataClass.aidl
new file mode 100644
index 000000000000..f14d47cde13f
--- /dev/null
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.aidl
@@ -0,0 +1,18 @@
+/*
+ * 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 com.android.codegentest;
+
+parcelable SampleDataClass;
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
new file mode 100644
index 000000000000..f0c5baad222b
--- /dev/null
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -0,0 +1,1808 @@
+/*
+ * 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 com.android.codegentest;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.annotation.StringDef;
+import android.annotation.StringRes;
+import android.annotation.UserIdInt;
+import android.net.LinkAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.DataClass.Each;
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * Sample data class, showing off various code generation features.
+ *
+ * See javadoc on non-generated code for the explanation of the various features.
+ *
+ * See {@link SampleDataClassTest} for various invariants the generated code is expected to hold.
+ */
+@DataClass(
+// genParcelable = true, // implied by `implements Parcelable`
+// genAidl = true, // implied by `implements Parcelable`
+// genGetters = true, // on by default
+// genConstDefs = true, // implied by presence of constants with common prefix
+ genBuilder = true, // on by default if optional fields present, but suppressed by
+ // genConstructor
+ genConstructor = true, // on by default but normally suppressed by genBuilder
+ genEqualsHashCode = true,
+ genToString = true,
+ genForEachField = true,
+ genSetters = true
+)
+public final class SampleDataClass implements Parcelable {
+
+ /**
+ * For any group of {@link int} or {@link String} constants like these, a corresponding
+ * {@link IntDef}/{@link StringDef} will get generated, with name based on common prefix
+ * by default.
+ *
+ * When {@link #SampleDataClass constructing} an instance, fields annotated with these
+ * annotations get automatically validated, with only provided constants being a valid value.
+ *
+ * @see StateName, the generated {@link StringDef}
+ * @see #mStateName annotated with {@link StateName}
+ */
+ public static final String STATE_NAME_UNDEFINED = "?";
+ public static final String STATE_NAME_ON = "on";
+ public static final String STATE_NAME_OFF = "off";
+
+ /**
+ * Additionally, for any generated {@link IntDef} a corresponding static
+ * *ToString method will be also generated, and used in {@link #toString()}.
+ *
+ * @see #stateToString(int)
+ * @see #toString()
+ * @see State
+ */
+ public static final int STATE_UNDEFINED = -1;
+ public static final int STATE_ON = 1;
+ public static final int STATE_OFF = 0;
+
+ /**
+ * {@link IntDef}s with values specified in hex("0x...") are considered to be
+ * {@link IntDef#flag flags}, while ones specified with regular int literals are considered
+ * not to be flags.
+ *
+ * This affects their string representation, e.g. see the difference in
+ * {@link #requestFlagsToString} vs {@link #stateToString}.
+ *
+ * This also affects the validation logic when {@link #SampleDataClass constructing}
+ * an instance, with any flag combination("|") being valid.
+ *
+ * You can customize the name of the generated {@link IntDef}/{@link StringDef} annotation
+ * by annotating each constant with the desired name before running the generation.
+ *
+ * Here the annotation is named {@link RequestFlags} instead of the default {@code Flags}.
+ */
+ public static final @RequestFlags int FLAG_MANUAL_REQUEST = 0x1;
+ public static final @RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST = 0x2;
+ public static final @RequestFlags int FLAG_AUGMENTED_REQUEST = 0x80000000;
+
+
+ /**
+ * Any property javadoc should go onto the field, and will be copied where appropriate,
+ * including getters, constructor parameters, builder setters, etc.
+ *
+ * <p>
+ * This allows to avoid the burden of maintaining copies of the same documentation
+ * pieces in multiple places for each field.
+ */
+ private int mNum;
+ /**
+ * Various javadoc features should work as expected when copied, e.g {@code code},
+ * {@link #mName links}, <a href="https://google.com">html links</a>, etc.
+ *
+ * @see #mNum2 ..and so should blocks at the bottom, e.g. {@code @see} blocks.
+ */
+ private int mNum2;
+ /**
+ * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the
+ * desired public API surface.
+ *
+ * @see #getNum4() is hidden
+ * @see Builder#setNum4(int) also hidden
+ * @hide
+ */
+ private int mNum4;
+
+ /**
+ * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields.
+ */
+ private @Nullable String mName;
+ /**
+ * Fields with default value expressions ("mFoo = ...") are optional, and are automatically
+ * initialized to the provided default expression, unless explicitly set.
+ *
+ * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter}
+ * while mandatory fields are passed via {@link Builder#Builder constructor}.
+ */
+ private @NonNull String mName2 = "Bob";
+ /**
+ * Alternatively, when default value computation is expensive,
+ * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value.
+ */
+ private @NonNull String mName4;
+ private static String defaultName4() {
+ // Expensive computation
+ return "Bob4";
+ }
+
+ /**
+ * For parcelling, any field type supported by {@link Parcel} is supported out of the box.
+ * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc.
+ */
+ private @Nullable AccessibilityNodeInfo mOtherParcelable = null;
+ /**
+ * Additionally, support for parcelling other types can be added by implementing a
+ * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation.
+ *
+ * @see MyDateParcelling an example {@link Parcelling} implementation
+ */
+ @DataClass.ParcelWith(MyDateParcelling.class)
+ private @NonNull Date mDate = new Date(42 * 42);
+ /**
+ * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn}
+ * to encourage its reuse.
+ */
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForPattern.class)
+ private @NonNull Pattern mPattern = Pattern.compile("");
+
+ /**
+ * For lists, when using a {@link Builder}, other than a regular
+ * {@link Builder#setLinkAddresses2(List) setter}, and additional
+ * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience.
+ */
+ private @NonNull List<LinkAddress> mLinkAddresses2 = new ArrayList<>();
+ /**
+ * For aesthetics, you may want to consider providing a singular version of the plural field
+ * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method.
+ *
+ * @see Builder#addLinkAddress(LinkAddress)
+ */
+ @DataClass.PluralOf("linkAddress")
+ private @NonNull ArrayList<LinkAddress> mLinkAddresses = new ArrayList<>();
+ /**
+ * For array fields, when using a {@link Builder}, vararg argument format is used for
+ * convenience.
+ *
+ * @see Builder#setLinkAddresses4(LinkAddress...)
+ */
+ private @Nullable LinkAddress[] mLinkAddresses4 = null;
+
+ /**
+ * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to
+ * getter/constructor/setter/builder parameters, making for a nicer api.
+ *
+ * @see #getStateName
+ * @see Builder#setStateName
+ */
+ private @StateName @NonNull String mStateName = STATE_NAME_UNDEFINED;
+ /**
+ * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
+ */
+ private @RequestFlags int mFlags;
+ /**
+ * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s
+ */
+ private @State int mState = STATE_UNDEFINED;
+
+
+ /**
+ * Making a field public will suppress getter generation in favor of accessing it directly.
+ */
+ public @NonNull CharSequence charSeq = "";
+ /**
+ * Final fields suppress generating a setter (when setters are requested).
+ */
+ private final @Nullable LinkAddress[] mLinkAddresses5;
+ /**
+ * Transient fields are completely ignored and can be used for caching.
+ */
+ private transient LinkAddress[] mLinkAddresses6;
+ /**
+ * When using transient fields for caching it's often also a good idea to initialize them
+ * lazily.
+ *
+ * You can declare a special method like {@link #lazyInitTmpStorage()}, to let the
+ * {@link #getTmpStorage getter} lazily-initialize the value on demand.
+ */
+ transient int[] mTmpStorage;
+ private int[] lazyInitTmpStorage() {
+ return new int[100];
+ }
+
+ /**
+ * Fields with certain annotations are automatically validated in constructor
+ *
+ * You can see overloads in {@link AnnotationValidations} for a list of currently
+ * supported ones.
+ *
+ * You can also extend support to your custom annotations by creating another corresponding
+ * overloads like
+ * {@link AnnotationValidations#validate(Class, UserIdInt, int)}.
+ *
+ * @see #SampleDataClass
+ */
+ private @StringRes int mStringRes = 0;
+ /**
+ * Validation annotations may also have parameters.
+ *
+ * Parameter values will be supplied to validation method as name-value pairs.
+ *
+ * @see AnnotationValidations#validate(Class, Size, int, String, int, String, int)
+ */
+ private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek = 3;
+ /**
+ * Unnamed validation annotation parameter gets supplied to the validating method named as
+ * "value".
+ *
+ * Validation annotations following {@link Each} annotation, will be applied for each
+ * array/collection element instead.
+ *
+ * @see AnnotationValidations#validate(Class, Size, int, String, int)
+ */
+ @Size(2)
+ @NonNull
+ @Each @FloatRange(from = 0f)
+ private float[] mCoords = new float[] {0f, 0f};
+
+
+ /**
+ * Manually declaring any method that would otherwise be generated suppresses its generation,
+ * allowing for fine-grained overrides of the generated behavior.
+ */
+ public LinkAddress[] getLinkAddresses4() {
+ //Suppress autogen
+ return null;
+ }
+
+ /**
+ * Additionally, some methods like {@link #equals}, {@link #hashCode}, {@link #toString},
+ * {@link #writeToParcel}, {@link Parcelable.Creator#createFromParcel} allow you to define
+ * special methods to override their behavior on a per-field basis.
+ *
+ * See the generateted methods' descriptions for the detailed instructions of what the method
+ * signatures for such methods are expected to be.
+ *
+ * Here we use this to "fix" {@link Pattern} not implementing equals/hashCode.
+ *
+ * @see #equals
+ * @see #hashCode
+ */
+ private boolean patternEquals(Pattern other) {
+ return Objects.equals(mPattern.pattern(), other.pattern());
+ }
+ private int patternHashCode() {
+ return Objects.hashCode(mPattern.pattern());
+ }
+
+ /**
+ * Similarly, {@link #onConstructed()}, if defined, gets called at the end of constructing an
+ * instance.
+ *
+ * At this point all fields should be in place, so this is the right place to put any custom
+ * validation logic.
+ */
+ private void onConstructed() {
+ Preconditions.checkState(mNum2 == mNum4);
+ }
+
+ /**
+ * {@link DataClass#genForEachField} can be used to generate a generic {@link #forEachField}
+ * utility, which can be used for various use-cases not covered out of the box.
+ * Callback passed to {@link #forEachField} will be called once per each property with its name
+ * and value.
+ *
+ * Here for example it's used to implement a typical dump method.
+ *
+ * Note that there are 2 {@link #forEachField} versions provided, one that treats each field
+ * value as an {@link Object}, thus boxing primitives if any, and one that additionally takes
+ * specialized callbacks for particular primitive field types used in given class.
+ *
+ * Some primitives like {@link Boolean}s and {@link Integer}s within [-128, 127] don't allocate
+ * when boxed, so it's up to you to decide which one to use for a given use-case.
+ */
+ public void dump(PrintWriter pw) {
+ forEachField((self, name, value) -> {
+ pw.append(" ").append(name).append(": ").append(String.valueOf(value)).append('\n');
+ });
+ }
+
+
+
+ // Code below generated by codegen v1.0.1.
+ //
+ // DO NOT MODIFY!
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+ //
+ // CHECKSTYLE:OFF Generated code
+
+ @IntDef(prefix = "STATE_", value = {
+ STATE_UNDEFINED,
+ STATE_ON,
+ STATE_OFF
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface State {}
+
+ @DataClass.Generated.Member
+ public static String stateToString(@State int value) {
+ switch (value) {
+ case STATE_UNDEFINED:
+ return "STATE_UNDEFINED";
+ case STATE_ON:
+ return "STATE_ON";
+ case STATE_OFF:
+ return "STATE_OFF";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @IntDef(flag = true, prefix = "FLAG_", value = {
+ FLAG_MANUAL_REQUEST,
+ FLAG_COMPATIBILITY_MODE_REQUEST,
+ FLAG_AUGMENTED_REQUEST
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface RequestFlags {}
+
+ @DataClass.Generated.Member
+ public static String requestFlagsToString(@RequestFlags int value) {
+ return com.android.internal.util.BitUtils.flagsToString(
+ value, SampleDataClass::singleRequestFlagsToString);
+ }
+
+ @DataClass.Generated.Member
+ static String singleRequestFlagsToString(@RequestFlags int value) {
+ switch (value) {
+ case FLAG_MANUAL_REQUEST:
+ return "FLAG_MANUAL_REQUEST";
+ case FLAG_COMPATIBILITY_MODE_REQUEST:
+ return "FLAG_COMPATIBILITY_MODE_REQUEST";
+ case FLAG_AUGMENTED_REQUEST:
+ return "FLAG_AUGMENTED_REQUEST";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @StringDef(prefix = "STATE_NAME_", value = {
+ STATE_NAME_UNDEFINED,
+ STATE_NAME_ON,
+ STATE_NAME_OFF
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface StateName {}
+
+ /**
+ * Creates a new SampleDataClass.
+ *
+ * @param num
+ * Any property javadoc should go onto the field, and will be copied where appropriate,
+ * including getters, constructor parameters, builder setters, etc.
+ *
+ * <p>
+ * This allows to avoid the burden of maintaining copies of the same documentation
+ * pieces in multiple places for each field.
+ * @param num2
+ * Various javadoc features should work as expected when copied, e.g {@code code},
+ * {@link #mName links}, <a href="https://google.com">html links</a>, etc.
+ * @param num4
+ * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the
+ * desired public API surface.
+ * @param name
+ * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields.
+ * @param name2
+ * Fields with default value expressions ("mFoo = ...") are optional, and are automatically
+ * initialized to the provided default expression, unless explicitly set.
+ *
+ * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter}
+ * while mandatory fields are passed via {@link Builder#Builder constructor}.
+ * @param name4
+ * Alternatively, when default value computation is expensive,
+ * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value.
+ * @param otherParcelable
+ * For parcelling, any field type supported by {@link Parcel} is supported out of the box.
+ * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc.
+ * @param date
+ * Additionally, support for parcelling other types can be added by implementing a
+ * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation.
+ * @param pattern
+ * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn}
+ * to encourage its reuse.
+ * @param linkAddresses2
+ * For lists, when using a {@link Builder}, other than a regular
+ * {@link Builder#setLinkAddresses2(List) setter}, and additional
+ * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience.
+ * @param linkAddresses
+ * For aesthetics, you may want to consider providing a singular version of the plural field
+ * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method.
+ * @param linkAddresses4
+ * For array fields, when using a {@link Builder}, vararg argument format is used for
+ * convenience.
+ * @param stateName
+ * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to
+ * getter/constructor/setter/builder parameters, making for a nicer api.
+ * @param flags
+ * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
+ * @param state
+ * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s
+ * @param charSeq
+ * Making a field public will suppress getter generation in favor of accessing it directly.
+ * @param linkAddresses5
+ * Final fields suppress generating a setter (when setters are requested).
+ * @param stringRes
+ * Fields with certain annotations are automatically validated in constructor
+ *
+ * You can see overloads in {@link AnnotationValidations} for a list of currently
+ * supported ones.
+ *
+ * You can also extend support to your custom annotations by creating another corresponding
+ * overloads like
+ * {@link AnnotationValidations#validate(Class, UserIdInt, int)}.
+ * @param dayOfWeek
+ * Validation annotations may also have parameters.
+ *
+ * Parameter values will be supplied to validation method as name-value pairs.
+ * @param coords
+ * Unnamed validation annotation parameter gets supplied to the validating method named as
+ * "value".
+ *
+ * Validation annotations following {@link Each} annotation, will be applied for each
+ * array/collection element instead.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass(
+ int num,
+ int num2,
+ int num4,
+ @Nullable String name,
+ @NonNull String name2,
+ @NonNull String name4,
+ @Nullable AccessibilityNodeInfo otherParcelable,
+ @NonNull Date date,
+ @NonNull Pattern pattern,
+ @NonNull List<LinkAddress> linkAddresses2,
+ @NonNull ArrayList<LinkAddress> linkAddresses,
+ @Nullable LinkAddress[] linkAddresses4,
+ @StateName @NonNull String stateName,
+ @RequestFlags int flags,
+ @State int state,
+ @NonNull CharSequence charSeq,
+ @Nullable LinkAddress[] linkAddresses5,
+ @StringRes int stringRes,
+ @android.annotation.IntRange(from = 0, to = 6) int dayOfWeek,
+ @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] coords) {
+ this.mNum = num;
+ this.mNum2 = num2;
+ this.mNum4 = num4;
+ this.mName = name;
+ this.mName2 = name2;
+ AnnotationValidations.validate(
+ NonNull.class, null, mName2);
+ this.mName4 = name4;
+ AnnotationValidations.validate(
+ NonNull.class, null, mName4);
+ this.mOtherParcelable = otherParcelable;
+ this.mDate = date;
+ AnnotationValidations.validate(
+ NonNull.class, null, mDate);
+ this.mPattern = pattern;
+ AnnotationValidations.validate(
+ NonNull.class, null, mPattern);
+ this.mLinkAddresses2 = linkAddresses2;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses2);
+ this.mLinkAddresses = linkAddresses;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses);
+ this.mLinkAddresses4 = linkAddresses4;
+ this.mStateName = stateName;
+
+ if (!(Objects.equals(mStateName, STATE_NAME_UNDEFINED))
+ && !(Objects.equals(mStateName, STATE_NAME_ON))
+ && !(Objects.equals(mStateName, STATE_NAME_OFF))) {
+ throw new java.lang.IllegalArgumentException(
+ "stateName was " + mStateName + " but must be one of: "
+ + "STATE_NAME_UNDEFINED(" + STATE_NAME_UNDEFINED + "), "
+ + "STATE_NAME_ON(" + STATE_NAME_ON + "), "
+ + "STATE_NAME_OFF(" + STATE_NAME_OFF + ")");
+ }
+
+ AnnotationValidations.validate(
+ NonNull.class, null, mStateName);
+ this.mFlags = flags;
+
+ Preconditions.checkFlagsArgument(
+ mFlags,
+ FLAG_MANUAL_REQUEST
+ | FLAG_COMPATIBILITY_MODE_REQUEST
+ | FLAG_AUGMENTED_REQUEST);
+ this.mState = state;
+
+ if (!(mState == STATE_UNDEFINED)
+ && !(mState == STATE_ON)
+ && !(mState == STATE_OFF)) {
+ throw new java.lang.IllegalArgumentException(
+ "state was " + mState + " but must be one of: "
+ + "STATE_UNDEFINED(" + STATE_UNDEFINED + "), "
+ + "STATE_ON(" + STATE_ON + "), "
+ + "STATE_OFF(" + STATE_OFF + ")");
+ }
+
+ this.charSeq = charSeq;
+ AnnotationValidations.validate(
+ NonNull.class, null, charSeq);
+ this.mLinkAddresses5 = linkAddresses5;
+ this.mStringRes = stringRes;
+ AnnotationValidations.validate(
+ StringRes.class, null, mStringRes);
+ this.mDayOfWeek = dayOfWeek;
+ AnnotationValidations.validate(
+ android.annotation.IntRange.class, null, mDayOfWeek,
+ "from", 0,
+ "to", 6);
+ this.mCoords = coords;
+ AnnotationValidations.validate(
+ Size.class, null, mCoords.length,
+ "value", 2);
+ AnnotationValidations.validate(
+ NonNull.class, null, mCoords);
+ int coordsSize = mCoords.length;
+ for (int i = 0; i < coordsSize; i++) {
+ AnnotationValidations.validate(
+ FloatRange.class, null, mCoords[i],
+ "from", 0f);
+ }
+
+
+ onConstructed();
+ }
+
+ /**
+ * Any property javadoc should go onto the field, and will be copied where appropriate,
+ * including getters, constructor parameters, builder setters, etc.
+ *
+ * <p>
+ * This allows to avoid the burden of maintaining copies of the same documentation
+ * pieces in multiple places for each field.
+ */
+ @DataClass.Generated.Member
+ public int getNum() {
+ return mNum;
+ }
+
+ /**
+ * Various javadoc features should work as expected when copied, e.g {@code code},
+ * {@link #mName links}, <a href="https://google.com">html links</a>, etc.
+ *
+ * @see #mNum2 ..and so should blocks at the bottom, e.g. {@code @see} blocks.
+ */
+ @DataClass.Generated.Member
+ public int getNum2() {
+ return mNum2;
+ }
+
+ /**
+ * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the
+ * desired public API surface.
+ *
+ * @see #getNum4() is hidden
+ * @see Builder#setNum4(int) also hidden
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public int getNum4() {
+ return mNum4;
+ }
+
+ /**
+ * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getName() {
+ return mName;
+ }
+
+ /**
+ * Fields with default value expressions ("mFoo = ...") are optional, and are automatically
+ * initialized to the provided default expression, unless explicitly set.
+ *
+ * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter}
+ * while mandatory fields are passed via {@link Builder#Builder constructor}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getName2() {
+ return mName2;
+ }
+
+ /**
+ * Alternatively, when default value computation is expensive,
+ * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getName4() {
+ return mName4;
+ }
+
+ /**
+ * For parcelling, any field type supported by {@link Parcel} is supported out of the box.
+ * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc.
+ */
+ @DataClass.Generated.Member
+ public @Nullable AccessibilityNodeInfo getOtherParcelable() {
+ return mOtherParcelable;
+ }
+
+ /**
+ * Additionally, support for parcelling other types can be added by implementing a
+ * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation.
+ *
+ * @see MyDateParcelling an example {@link Parcelling} implementation
+ */
+ @DataClass.Generated.Member
+ public @NonNull Date getDate() {
+ return mDate;
+ }
+
+ /**
+ * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn}
+ * to encourage its reuse.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Pattern getPattern() {
+ return mPattern;
+ }
+
+ /**
+ * For lists, when using a {@link Builder}, other than a regular
+ * {@link Builder#setLinkAddresses2(List) setter}, and additional
+ * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<LinkAddress> getLinkAddresses2() {
+ return mLinkAddresses2;
+ }
+
+ /**
+ * For aesthetics, you may want to consider providing a singular version of the plural field
+ * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method.
+ *
+ * @see Builder#addLinkAddress(LinkAddress)
+ */
+ @DataClass.Generated.Member
+ public @NonNull ArrayList<LinkAddress> getLinkAddresses() {
+ return mLinkAddresses;
+ }
+
+ /**
+ * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to
+ * getter/constructor/setter/builder parameters, making for a nicer api.
+ *
+ * @see #getStateName
+ * @see Builder#setStateName
+ */
+ @DataClass.Generated.Member
+ public @StateName @NonNull String getStateName() {
+ return mStateName;
+ }
+
+ /**
+ * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
+ */
+ @DataClass.Generated.Member
+ public @RequestFlags int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s
+ */
+ @DataClass.Generated.Member
+ public @State int getState() {
+ return mState;
+ }
+
+ /**
+ * Final fields suppress generating a setter (when setters are requested).
+ */
+ @DataClass.Generated.Member
+ public @Nullable LinkAddress[] getLinkAddresses5() {
+ return mLinkAddresses5;
+ }
+
+ /**
+ * Fields with certain annotations are automatically validated in constructor
+ *
+ * You can see overloads in {@link AnnotationValidations} for a list of currently
+ * supported ones.
+ *
+ * You can also extend support to your custom annotations by creating another corresponding
+ * overloads like
+ * {@link AnnotationValidations#validate(Class, UserIdInt, int)}.
+ *
+ * @see #SampleDataClass
+ */
+ @DataClass.Generated.Member
+ public @StringRes int getStringRes() {
+ return mStringRes;
+ }
+
+ /**
+ * Validation annotations may also have parameters.
+ *
+ * Parameter values will be supplied to validation method as name-value pairs.
+ *
+ * @see AnnotationValidations#validate(Class, Size, int, String, int, String, int)
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.IntRange(from = 0, to = 6) int getDayOfWeek() {
+ return mDayOfWeek;
+ }
+
+ /**
+ * Unnamed validation annotation parameter gets supplied to the validating method named as
+ * "value".
+ *
+ * Validation annotations following {@link Each} annotation, will be applied for each
+ * array/collection element instead.
+ *
+ * @see AnnotationValidations#validate(Class, Size, int, String, int)
+ */
+ @DataClass.Generated.Member
+ public @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] getCoords() {
+ return mCoords;
+ }
+
+ /**
+ * When using transient fields for caching it's often also a good idea to initialize them
+ * lazily.
+ *
+ * You can declare a special method like {@link #lazyInitTmpStorage()}, to let the
+ * {@link #getTmpStorage getter} lazily-initialize the value on demand.
+ */
+ @DataClass.Generated.Member
+ public int[] getTmpStorage() {
+ int[] tmpStorage = mTmpStorage;
+ if (tmpStorage == null) {
+ // You can mark field as volatile for thread-safe double-check init
+ tmpStorage = mTmpStorage = lazyInitTmpStorage();
+ }
+ return tmpStorage;
+ }
+
+ /**
+ * Any property javadoc should go onto the field, and will be copied where appropriate,
+ * including getters, constructor parameters, builder setters, etc.
+ *
+ * <p>
+ * This allows to avoid the burden of maintaining copies of the same documentation
+ * pieces in multiple places for each field.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setNum(int value) {
+ mNum = value;
+ return this;
+ }
+
+ /**
+ * Various javadoc features should work as expected when copied, e.g {@code code},
+ * {@link #mName links}, <a href="https://google.com">html links</a>, etc.
+ *
+ * @see #mNum2 ..and so should blocks at the bottom, e.g. {@code @see} blocks.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setNum2(int value) {
+ mNum2 = value;
+ return this;
+ }
+
+ /**
+ * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the
+ * desired public API surface.
+ *
+ * @see #getNum4() is hidden
+ * @see Builder#setNum4(int) also hidden
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setNum4(int value) {
+ mNum4 = value;
+ return this;
+ }
+
+ /**
+ * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setName(@Nullable String value) {
+ mName = value;
+ return this;
+ }
+
+ /**
+ * Fields with default value expressions ("mFoo = ...") are optional, and are automatically
+ * initialized to the provided default expression, unless explicitly set.
+ *
+ * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter}
+ * while mandatory fields are passed via {@link Builder#Builder constructor}.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setName2(@NonNull String value) {
+ mName2 = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mName2);
+ return this;
+ }
+
+ /**
+ * Alternatively, when default value computation is expensive,
+ * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setName4(@NonNull String value) {
+ mName4 = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mName4);
+ return this;
+ }
+
+ /**
+ * For parcelling, any field type supported by {@link Parcel} is supported out of the box.
+ * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setOtherParcelable(@Nullable AccessibilityNodeInfo value) {
+ mOtherParcelable = value;
+ return this;
+ }
+
+ /**
+ * Additionally, support for parcelling other types can be added by implementing a
+ * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation.
+ *
+ * @see MyDateParcelling an example {@link Parcelling} implementation
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setDate(@NonNull Date value) {
+ mDate = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mDate);
+ return this;
+ }
+
+ /**
+ * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn}
+ * to encourage its reuse.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setPattern(@NonNull Pattern value) {
+ mPattern = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mPattern);
+ return this;
+ }
+
+ /**
+ * For lists, when using a {@link Builder}, other than a regular
+ * {@link Builder#setLinkAddresses2(List) setter}, and additional
+ * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setLinkAddresses2(@NonNull List<LinkAddress> value) {
+ mLinkAddresses2 = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses2);
+ return this;
+ }
+
+ /**
+ * For aesthetics, you may want to consider providing a singular version of the plural field
+ * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method.
+ *
+ * @see Builder#addLinkAddress(LinkAddress)
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setLinkAddresses(@NonNull ArrayList<LinkAddress> value) {
+ mLinkAddresses = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses);
+ return this;
+ }
+
+ /**
+ * For array fields, when using a {@link Builder}, vararg argument format is used for
+ * convenience.
+ *
+ * @see Builder#setLinkAddresses4(LinkAddress...)
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setLinkAddresses4(@Nullable LinkAddress... value) {
+ mLinkAddresses4 = value;
+ return this;
+ }
+
+ /**
+ * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to
+ * getter/constructor/setter/builder parameters, making for a nicer api.
+ *
+ * @see #getStateName
+ * @see Builder#setStateName
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setStateName(@StateName @NonNull String value) {
+ mStateName = value;
+
+ if (!(Objects.equals(mStateName, STATE_NAME_UNDEFINED))
+ && !(Objects.equals(mStateName, STATE_NAME_ON))
+ && !(Objects.equals(mStateName, STATE_NAME_OFF))) {
+ throw new java.lang.IllegalArgumentException(
+ "stateName was " + mStateName + " but must be one of: "
+ + "STATE_NAME_UNDEFINED(" + STATE_NAME_UNDEFINED + "), "
+ + "STATE_NAME_ON(" + STATE_NAME_ON + "), "
+ + "STATE_NAME_OFF(" + STATE_NAME_OFF + ")");
+ }
+
+ AnnotationValidations.validate(
+ NonNull.class, null, mStateName);
+ return this;
+ }
+
+ /**
+ * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setFlags(@RequestFlags int value) {
+ mFlags = value;
+
+ Preconditions.checkFlagsArgument(
+ mFlags,
+ FLAG_MANUAL_REQUEST
+ | FLAG_COMPATIBILITY_MODE_REQUEST
+ | FLAG_AUGMENTED_REQUEST);
+ return this;
+ }
+
+ /**
+ * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setState(@State int value) {
+ mState = value;
+
+ if (!(mState == STATE_UNDEFINED)
+ && !(mState == STATE_ON)
+ && !(mState == STATE_OFF)) {
+ throw new java.lang.IllegalArgumentException(
+ "state was " + mState + " but must be one of: "
+ + "STATE_UNDEFINED(" + STATE_UNDEFINED + "), "
+ + "STATE_ON(" + STATE_ON + "), "
+ + "STATE_OFF(" + STATE_OFF + ")");
+ }
+
+ return this;
+ }
+
+ /**
+ * Fields with certain annotations are automatically validated in constructor
+ *
+ * You can see overloads in {@link AnnotationValidations} for a list of currently
+ * supported ones.
+ *
+ * You can also extend support to your custom annotations by creating another corresponding
+ * overloads like
+ * {@link AnnotationValidations#validate(Class, UserIdInt, int)}.
+ *
+ * @see #SampleDataClass
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setStringRes(@StringRes int value) {
+ mStringRes = value;
+ AnnotationValidations.validate(
+ StringRes.class, null, mStringRes);
+ return this;
+ }
+
+ /**
+ * Validation annotations may also have parameters.
+ *
+ * Parameter values will be supplied to validation method as name-value pairs.
+ *
+ * @see AnnotationValidations#validate(Class, Size, int, String, int, String, int)
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setDayOfWeek(@android.annotation.IntRange(from = 0, to = 6) int value) {
+ mDayOfWeek = value;
+ AnnotationValidations.validate(
+ android.annotation.IntRange.class, null, mDayOfWeek,
+ "from", 0,
+ "to", 6);
+ return this;
+ }
+
+ /**
+ * Unnamed validation annotation parameter gets supplied to the validating method named as
+ * "value".
+ *
+ * Validation annotations following {@link Each} annotation, will be applied for each
+ * array/collection element instead.
+ *
+ * @see AnnotationValidations#validate(Class, Size, int, String, int)
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setCoords(@Size(2) @NonNull @Each @FloatRange(from = 0f) float... value) {
+ mCoords = value;
+ AnnotationValidations.validate(
+ Size.class, null, mCoords.length,
+ "value", 2);
+ AnnotationValidations.validate(
+ NonNull.class, null, mCoords);
+ int coordsSize = mCoords.length;
+ for (int i = 0; i < coordsSize; i++) {
+ AnnotationValidations.validate(
+ FloatRange.class, null, mCoords[i],
+ "from", 0f);
+ }
+
+ return this;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "SampleDataClass { " +
+ "num = " + mNum + ", " +
+ "num2 = " + mNum2 + ", " +
+ "num4 = " + mNum4 + ", " +
+ "name = " + mName + ", " +
+ "name2 = " + mName2 + ", " +
+ "name4 = " + mName4 + ", " +
+ "otherParcelable = " + mOtherParcelable + ", " +
+ "date = " + mDate + ", " +
+ "pattern = " + mPattern + ", " +
+ "linkAddresses2 = " + mLinkAddresses2 + ", " +
+ "linkAddresses = " + mLinkAddresses + ", " +
+ "linkAddresses4 = " + java.util.Arrays.toString(mLinkAddresses4) + ", " +
+ "stateName = " + mStateName + ", " +
+ "flags = " + requestFlagsToString(mFlags) + ", " +
+ "state = " + stateToString(mState) + ", " +
+ "charSeq = " + charSeq + ", " +
+ "linkAddresses5 = " + java.util.Arrays.toString(mLinkAddresses5) + ", " +
+ "stringRes = " + mStringRes + ", " +
+ "dayOfWeek = " + mDayOfWeek + ", " +
+ "coords = " + java.util.Arrays.toString(mCoords) +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(SampleDataClass other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ SampleDataClass that = (SampleDataClass) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mNum == that.mNum
+ && mNum2 == that.mNum2
+ && mNum4 == that.mNum4
+ && Objects.equals(mName, that.mName)
+ && Objects.equals(mName2, that.mName2)
+ && Objects.equals(mName4, that.mName4)
+ && Objects.equals(mOtherParcelable, that.mOtherParcelable)
+ && Objects.equals(mDate, that.mDate)
+ && patternEquals(that.mPattern)
+ && Objects.equals(mLinkAddresses2, that.mLinkAddresses2)
+ && Objects.equals(mLinkAddresses, that.mLinkAddresses)
+ && java.util.Arrays.equals(mLinkAddresses4, that.mLinkAddresses4)
+ && Objects.equals(mStateName, that.mStateName)
+ && mFlags == that.mFlags
+ && mState == that.mState
+ && Objects.equals(charSeq, that.charSeq)
+ && java.util.Arrays.equals(mLinkAddresses5, that.mLinkAddresses5)
+ && mStringRes == that.mStringRes
+ && mDayOfWeek == that.mDayOfWeek
+ && java.util.Arrays.equals(mCoords, that.mCoords);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mNum;
+ _hash = 31 * _hash + mNum2;
+ _hash = 31 * _hash + mNum4;
+ _hash = 31 * _hash + Objects.hashCode(mName);
+ _hash = 31 * _hash + Objects.hashCode(mName2);
+ _hash = 31 * _hash + Objects.hashCode(mName4);
+ _hash = 31 * _hash + Objects.hashCode(mOtherParcelable);
+ _hash = 31 * _hash + Objects.hashCode(mDate);
+ _hash = 31 * _hash + patternHashCode();
+ _hash = 31 * _hash + Objects.hashCode(mLinkAddresses2);
+ _hash = 31 * _hash + Objects.hashCode(mLinkAddresses);
+ _hash = 31 * _hash + java.util.Arrays.hashCode(mLinkAddresses4);
+ _hash = 31 * _hash + Objects.hashCode(mStateName);
+ _hash = 31 * _hash + mFlags;
+ _hash = 31 * _hash + mState;
+ _hash = 31 * _hash + Objects.hashCode(charSeq);
+ _hash = 31 * _hash + java.util.Arrays.hashCode(mLinkAddresses5);
+ _hash = 31 * _hash + mStringRes;
+ _hash = 31 * _hash + mDayOfWeek;
+ _hash = 31 * _hash + java.util.Arrays.hashCode(mCoords);
+ return _hash;
+ }
+
+ @DataClass.Generated.Member
+ void forEachField(
+ DataClass.PerIntFieldAction<SampleDataClass> actionInt,
+ DataClass.PerObjectFieldAction<SampleDataClass> actionObject) {
+ actionInt.acceptInt(this, "num", mNum);
+ actionInt.acceptInt(this, "num2", mNum2);
+ actionInt.acceptInt(this, "num4", mNum4);
+ actionObject.acceptObject(this, "name", mName);
+ actionObject.acceptObject(this, "name2", mName2);
+ actionObject.acceptObject(this, "name4", mName4);
+ actionObject.acceptObject(this, "otherParcelable", mOtherParcelable);
+ actionObject.acceptObject(this, "date", mDate);
+ actionObject.acceptObject(this, "pattern", mPattern);
+ actionObject.acceptObject(this, "linkAddresses2", mLinkAddresses2);
+ actionObject.acceptObject(this, "linkAddresses", mLinkAddresses);
+ actionObject.acceptObject(this, "linkAddresses4", mLinkAddresses4);
+ actionObject.acceptObject(this, "stateName", mStateName);
+ actionInt.acceptInt(this, "flags", mFlags);
+ actionInt.acceptInt(this, "state", mState);
+ actionObject.acceptObject(this, "charSeq", charSeq);
+ actionObject.acceptObject(this, "linkAddresses5", mLinkAddresses5);
+ actionInt.acceptInt(this, "stringRes", mStringRes);
+ actionInt.acceptInt(this, "dayOfWeek", mDayOfWeek);
+ actionObject.acceptObject(this, "coords", mCoords);
+ }
+
+ /** @deprecated May cause boxing allocations - use with caution! */
+ @Deprecated
+ @DataClass.Generated.Member
+ void forEachField(DataClass.PerObjectFieldAction<SampleDataClass> action) {
+ action.acceptObject(this, "num", mNum);
+ action.acceptObject(this, "num2", mNum2);
+ action.acceptObject(this, "num4", mNum4);
+ action.acceptObject(this, "name", mName);
+ action.acceptObject(this, "name2", mName2);
+ action.acceptObject(this, "name4", mName4);
+ action.acceptObject(this, "otherParcelable", mOtherParcelable);
+ action.acceptObject(this, "date", mDate);
+ action.acceptObject(this, "pattern", mPattern);
+ action.acceptObject(this, "linkAddresses2", mLinkAddresses2);
+ action.acceptObject(this, "linkAddresses", mLinkAddresses);
+ action.acceptObject(this, "linkAddresses4", mLinkAddresses4);
+ action.acceptObject(this, "stateName", mStateName);
+ action.acceptObject(this, "flags", mFlags);
+ action.acceptObject(this, "state", mState);
+ action.acceptObject(this, "charSeq", charSeq);
+ action.acceptObject(this, "linkAddresses5", mLinkAddresses5);
+ action.acceptObject(this, "stringRes", mStringRes);
+ action.acceptObject(this, "dayOfWeek", mDayOfWeek);
+ action.acceptObject(this, "coords", mCoords);
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<Date> sParcellingForDate =
+ Parcelling.Cache.get(
+ MyDateParcelling.class);
+ static {
+ if (sParcellingForDate == null) {
+ sParcellingForDate = Parcelling.Cache.put(
+ new MyDateParcelling());
+ }
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<Pattern> sParcellingForPattern =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForPattern.class);
+ static {
+ if (sParcellingForPattern == null) {
+ sParcellingForPattern = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForPattern());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ long flg = 0;
+ if (mName != null) flg |= 0x8;
+ if (mOtherParcelable != null) flg |= 0x40;
+ if (mLinkAddresses4 != null) flg |= 0x800;
+ if (mLinkAddresses5 != null) flg |= 0x10000;
+ dest.writeLong(flg);
+ dest.writeInt(mNum);
+ dest.writeInt(mNum2);
+ dest.writeInt(mNum4);
+ if (mName != null) dest.writeString(mName);
+ dest.writeString(mName2);
+ dest.writeString(mName4);
+ if (mOtherParcelable != null) dest.writeTypedObject(mOtherParcelable, flags);
+ sParcellingForDate.parcel(mDate, dest, flags);
+ sParcellingForPattern.parcel(mPattern, dest, flags);
+ dest.writeParcelableList(mLinkAddresses2, flags);
+ dest.writeParcelableList(mLinkAddresses, flags);
+ if (mLinkAddresses4 != null) dest.writeTypedArray(mLinkAddresses4, flags);
+ dest.writeString(mStateName);
+ dest.writeInt(mFlags);
+ dest.writeInt(mState);
+ dest.writeCharSequence(charSeq);
+ if (mLinkAddresses5 != null) dest.writeTypedArray(mLinkAddresses5, flags);
+ dest.writeInt(mStringRes);
+ dest.writeInt(mDayOfWeek);
+ dest.writeFloatArray(mCoords);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<SampleDataClass> CREATOR
+ = new Parcelable.Creator<SampleDataClass>() {
+ @Override
+ public SampleDataClass[] newArray(int size) {
+ return new SampleDataClass[size];
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ public SampleDataClass createFromParcel(Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ long flg = in.readLong();
+ int num = in.readInt();
+ int num2 = in.readInt();
+ int num4 = in.readInt();
+ String name = (flg & 0x8) == 0 ? null : in.readString();
+ String name2 = in.readString();
+ String name4 = in.readString();
+ AccessibilityNodeInfo otherParcelable = (flg & 0x40) == 0 ? null : (AccessibilityNodeInfo) in.readTypedObject(AccessibilityNodeInfo.CREATOR);
+ Date date = sParcellingForDate.unparcel(in);
+ Pattern pattern = sParcellingForPattern.unparcel(in);
+ List<LinkAddress> linkAddresses2 = new ArrayList<>();
+ in.readParcelableList(linkAddresses2, LinkAddress.class.getClassLoader());
+ ArrayList<LinkAddress> linkAddresses = new ArrayList<>();
+ in.readParcelableList(linkAddresses, LinkAddress.class.getClassLoader());
+ LinkAddress[] linkAddresses4 = (flg & 0x800) == 0 ? null : (LinkAddress[]) in.createTypedArray(LinkAddress.CREATOR);
+ String stateName = in.readString();
+ int flags = in.readInt();
+ int state = in.readInt();
+ CharSequence _charSeq = (CharSequence) in.readCharSequence();
+ LinkAddress[] linkAddresses5 = (flg & 0x10000) == 0 ? null : (LinkAddress[]) in.createTypedArray(LinkAddress.CREATOR);
+ int stringRes = in.readInt();
+ int dayOfWeek = in.readInt();
+ float[] coords = in.createFloatArray();
+ return new SampleDataClass(
+ num,
+ num2,
+ num4,
+ name,
+ name2,
+ name4,
+ otherParcelable,
+ date,
+ pattern,
+ linkAddresses2,
+ linkAddresses,
+ linkAddresses4,
+ stateName,
+ flags,
+ state,
+ _charSeq,
+ linkAddresses5,
+ stringRes,
+ dayOfWeek,
+ coords);
+ }
+ };
+
+ /**
+ * A builder for {@link SampleDataClass}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static class Builder {
+
+ private int mNum;
+ private int mNum2;
+ private int mNum4;
+ private @Nullable String mName;
+ private @NonNull String mName2;
+ private @NonNull String mName4;
+ private @Nullable AccessibilityNodeInfo mOtherParcelable;
+ private @NonNull Date mDate;
+ private @NonNull Pattern mPattern;
+ private @NonNull List<LinkAddress> mLinkAddresses2;
+ private @NonNull ArrayList<LinkAddress> mLinkAddresses;
+ private @Nullable LinkAddress[] mLinkAddresses4;
+ private @StateName @NonNull String mStateName;
+ private @RequestFlags int mFlags;
+ private @State int mState;
+ private @NonNull CharSequence charSeq;
+ private @Nullable LinkAddress[] mLinkAddresses5;
+ private @StringRes int mStringRes;
+ private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek;
+ private @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] mCoords;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param num
+ * Any property javadoc should go onto the field, and will be copied where appropriate,
+ * including getters, constructor parameters, builder setters, etc.
+ *
+ * <p>
+ * This allows to avoid the burden of maintaining copies of the same documentation
+ * pieces in multiple places for each field.
+ * @param num2
+ * Various javadoc features should work as expected when copied, e.g {@code code},
+ * {@link #mName links}, <a href="https://google.com">html links</a>, etc.
+ * @param num4
+ * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the
+ * desired public API surface.
+ * @param name
+ * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields.
+ * @param flags
+ * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
+ * @param linkAddresses5
+ * Final fields suppress generating a setter (when setters are requested).
+ */
+ public Builder(
+ int num,
+ int num2,
+ int num4,
+ @Nullable String name,
+ @RequestFlags int flags,
+ @Nullable LinkAddress[] linkAddresses5) {
+ mNum = num;
+ mNum2 = num2;
+ mNum4 = num4;
+ mName = name;
+ mFlags = flags;
+
+ Preconditions.checkFlagsArgument(
+ mFlags,
+ FLAG_MANUAL_REQUEST
+ | FLAG_COMPATIBILITY_MODE_REQUEST
+ | FLAG_AUGMENTED_REQUEST);
+ mLinkAddresses5 = linkAddresses5;
+ }
+
+ /**
+ * Any property javadoc should go onto the field, and will be copied where appropriate,
+ * including getters, constructor parameters, builder setters, etc.
+ *
+ * <p>
+ * This allows to avoid the burden of maintaining copies of the same documentation
+ * pieces in multiple places for each field.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setNum(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mNum = value;
+ return this;
+ }
+
+ /**
+ * Various javadoc features should work as expected when copied, e.g {@code code},
+ * {@link #mName links}, <a href="https://google.com">html links</a>, etc.
+ *
+ * @see #mNum2 ..and so should blocks at the bottom, e.g. {@code @see} blocks.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setNum2(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mNum2 = value;
+ return this;
+ }
+
+ /**
+ * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the
+ * desired public API surface.
+ *
+ * @see #getNum4() is hidden
+ * @see Builder#setNum4(int) also hidden
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setNum4(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mNum4 = value;
+ return this;
+ }
+
+ /**
+ * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setName(@Nullable String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mName = value;
+ return this;
+ }
+
+ /**
+ * Fields with default value expressions ("mFoo = ...") are optional, and are automatically
+ * initialized to the provided default expression, unless explicitly set.
+ *
+ * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter}
+ * while mandatory fields are passed via {@link Builder#Builder constructor}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setName2(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mName2 = value;
+ return this;
+ }
+
+ /**
+ * Alternatively, when default value computation is expensive,
+ * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setName4(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mName4 = value;
+ return this;
+ }
+
+ /**
+ * For parcelling, any field type supported by {@link Parcel} is supported out of the box.
+ * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setOtherParcelable(@Nullable AccessibilityNodeInfo value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x40;
+ mOtherParcelable = value;
+ return this;
+ }
+
+ /**
+ * Additionally, support for parcelling other types can be added by implementing a
+ * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation.
+ *
+ * @see MyDateParcelling an example {@link Parcelling} implementation
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setDate(@NonNull Date value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x80;
+ mDate = value;
+ return this;
+ }
+
+ /**
+ * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn}
+ * to encourage its reuse.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setPattern(@NonNull Pattern value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x100;
+ mPattern = value;
+ return this;
+ }
+
+ /**
+ * For lists, when using a {@link Builder}, other than a regular
+ * {@link Builder#setLinkAddresses2(List) setter}, and additional
+ * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setLinkAddresses2(@NonNull List<LinkAddress> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x200;
+ mLinkAddresses2 = value;
+ return this;
+ }
+
+ /** @see #setLinkAddresses2 */
+ @DataClass.Generated.Member
+ public @NonNull Builder addLinkAddresses2(LinkAddress value) {
+ // You can refine this method's name by providing item's singular name, e.g.:
+ // @DataClass.PluralOf("item")) mItems = ...
+
+ if (mLinkAddresses2 == null) setLinkAddresses2(new ArrayList<>());
+ mLinkAddresses2.add(value);
+ return this;
+ }
+
+ /**
+ * For aesthetics, you may want to consider providing a singular version of the plural field
+ * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method.
+ *
+ * @see Builder#addLinkAddress(LinkAddress)
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setLinkAddresses(@NonNull ArrayList<LinkAddress> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x400;
+ mLinkAddresses = value;
+ return this;
+ }
+
+ /** @see #setLinkAddresses */
+ @DataClass.Generated.Member
+ public @NonNull Builder addLinkAddress(LinkAddress value) {
+ if (mLinkAddresses == null) setLinkAddresses(new ArrayList<>());
+ mLinkAddresses.add(value);
+ return this;
+ }
+
+ /**
+ * For array fields, when using a {@link Builder}, vararg argument format is used for
+ * convenience.
+ *
+ * @see Builder#setLinkAddresses4(LinkAddress...)
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setLinkAddresses4(@Nullable LinkAddress... value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x800;
+ mLinkAddresses4 = value;
+ return this;
+ }
+
+ /**
+ * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to
+ * getter/constructor/setter/builder parameters, making for a nicer api.
+ *
+ * @see #getStateName
+ * @see Builder#setStateName
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setStateName(@StateName @NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1000;
+ mStateName = value;
+ return this;
+ }
+
+ /**
+ * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setFlags(@RequestFlags int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2000;
+ mFlags = value;
+ return this;
+ }
+
+ /**
+ * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setState(@State int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4000;
+ mState = value;
+ return this;
+ }
+
+ /**
+ * Making a field public will suppress getter generation in favor of accessing it directly.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setCharSeq(@NonNull CharSequence value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8000;
+ charSeq = value;
+ return this;
+ }
+
+ /**
+ * Final fields suppress generating a setter (when setters are requested).
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setLinkAddresses5(@Nullable LinkAddress... value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10000;
+ mLinkAddresses5 = value;
+ return this;
+ }
+
+ /**
+ * Fields with certain annotations are automatically validated in constructor
+ *
+ * You can see overloads in {@link AnnotationValidations} for a list of currently
+ * supported ones.
+ *
+ * You can also extend support to your custom annotations by creating another corresponding
+ * overloads like
+ * {@link AnnotationValidations#validate(Class, UserIdInt, int)}.
+ *
+ * @see #SampleDataClass
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setStringRes(@StringRes int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20000;
+ mStringRes = value;
+ return this;
+ }
+
+ /**
+ * Validation annotations may also have parameters.
+ *
+ * Parameter values will be supplied to validation method as name-value pairs.
+ *
+ * @see AnnotationValidations#validate(Class, Size, int, String, int, String, int)
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setDayOfWeek(@android.annotation.IntRange(from = 0, to = 6) int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x40000;
+ mDayOfWeek = value;
+ return this;
+ }
+
+ /**
+ * Unnamed validation annotation parameter gets supplied to the validating method named as
+ * "value".
+ *
+ * Validation annotations following {@link Each} annotation, will be applied for each
+ * array/collection element instead.
+ *
+ * @see AnnotationValidations#validate(Class, Size, int, String, int)
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setCoords(@Size(2) @NonNull @Each @FloatRange(from = 0f) float... value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x80000;
+ mCoords = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public SampleDataClass build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x100000; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x10) == 0) {
+ mName2 = "Bob";
+ }
+ if ((mBuilderFieldsSet & 0x20) == 0) {
+ mName4 = defaultName4();
+ }
+ if ((mBuilderFieldsSet & 0x40) == 0) {
+ mOtherParcelable = null;
+ }
+ if ((mBuilderFieldsSet & 0x80) == 0) {
+ mDate = new Date(42 * 42);
+ }
+ if ((mBuilderFieldsSet & 0x100) == 0) {
+ mPattern = Pattern.compile("");
+ }
+ if ((mBuilderFieldsSet & 0x200) == 0) {
+ mLinkAddresses2 = new ArrayList<>();
+ }
+ if ((mBuilderFieldsSet & 0x400) == 0) {
+ mLinkAddresses = new ArrayList<>();
+ }
+ if ((mBuilderFieldsSet & 0x800) == 0) {
+ mLinkAddresses4 = null;
+ }
+ if ((mBuilderFieldsSet & 0x1000) == 0) {
+ mStateName = STATE_NAME_UNDEFINED;
+ }
+ if ((mBuilderFieldsSet & 0x4000) == 0) {
+ mState = STATE_UNDEFINED;
+ }
+ if ((mBuilderFieldsSet & 0x8000) == 0) {
+ charSeq = "";
+ }
+ if ((mBuilderFieldsSet & 0x20000) == 0) {
+ mStringRes = 0;
+ }
+ if ((mBuilderFieldsSet & 0x40000) == 0) {
+ mDayOfWeek = 3;
+ }
+ if ((mBuilderFieldsSet & 0x80000) == 0) {
+ mCoords = new float[] { 0f, 0f };
+ }
+ SampleDataClass o = new SampleDataClass(
+ mNum,
+ mNum2,
+ mNum4,
+ mName,
+ mName2,
+ mName4,
+ mOtherParcelable,
+ mDate,
+ mPattern,
+ mLinkAddresses2,
+ mLinkAddresses,
+ mLinkAddresses4,
+ mStateName,
+ mFlags,
+ mState,
+ charSeq,
+ mLinkAddresses5,
+ mStringRes,
+ mDayOfWeek,
+ mCoords);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x100000) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1568235365376L,
+ codegenVersion = "1.0.1",
+ sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
+ inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_UNDEFINED\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange(from=0L, to=6L) int mDayOfWeek\nprivate @android.annotation.Size(2L) @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange(from=0.0) float[] mCoords\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
+ @Deprecated
+ private void __metadata() {}
+
+}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java b/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java
new file mode 100644
index 000000000000..663620743af9
--- /dev/null
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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 com.android.codegentest;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+
+import android.net.LinkAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Tests {@link SampleDataClass} after it's augmented with dataclass codegen.
+ *
+ * Use {@code $ . runTest.sh} to run.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SampleDataClassTest {
+
+ private SampleDataClass mSpecimen = newBuilder().build();
+
+ private static SampleDataClass.Builder newBuilder() {
+ return newInvalidBuilder()
+ .setNum(42)
+ .setNum2(42)
+ .setNum4(42)
+ .setName4("foobar")
+ .setLinkAddresses5();
+ }
+
+ private static SampleDataClass.Builder newInvalidBuilder() {
+ return new SampleDataClass.Builder(1, 2, 3, "a", 0, null)
+ .setName("some parcelable")
+ .setFlags(SampleDataClass.FLAG_MANUAL_REQUEST);
+ }
+
+ @Test
+ public void testParcelling_producesEqualInstance() {
+ SampleDataClass copy = parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
+ assertEquals(mSpecimen, copy);
+ assertEquals(mSpecimen.hashCode(), copy.hashCode());
+ }
+
+ @Test
+ public void testParcelling_producesInstanceWithEqualFields() {
+ SampleDataClass copy = parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
+ copy.forEachField((self, copyFieldName, copyFieldValue) -> {
+ mSpecimen.forEachField((self2, specimenFieldName, specimenFieldValue) -> {
+ if (copyFieldName.equals(specimenFieldName)
+ && !copyFieldName.equals("pattern")
+ && (specimenFieldValue == null
+ || !specimenFieldValue.getClass().isArray())) {
+ assertEquals("Mismatched field values for " + copyFieldName,
+ specimenFieldValue, copyFieldValue);
+ }
+ });
+ });
+ }
+
+ @Test
+ public void testCustomParcelling_instanceIsCached() {
+ parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
+ parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
+ assertEquals(1, MyDateParcelling.sInstanceCount.get());
+ }
+
+ @Test
+ public void testDefaultFieldValue_isPropagated() {
+ assertEquals(new Date(42 * 42), mSpecimen.getDate());
+ }
+
+ @Test
+ public void testForEachField_avoidsBoxing() {
+ AtomicInteger intFieldCount = new AtomicInteger(0);
+ mSpecimen.forEachField(
+ (self, name, intValue) -> intFieldCount.getAndIncrement(),
+ (self, name, objectValue) -> {
+ if (objectValue != null) {
+ assertThat("Boxed field " + name,
+ objectValue, not(instanceOf(Integer.class)));
+ }
+ });
+ assertThat(intFieldCount.get(), greaterThanOrEqualTo(1));
+ }
+
+ @Test
+ public void testToString_containsEachField() {
+ String toString = mSpecimen.toString();
+
+ mSpecimen.forEachField((self, name, value) -> {
+ assertThat(toString, containsString(name));
+ if (value instanceof Integer) {
+ // Could be flags, their special toString tested separately
+ } else if (value instanceof Object[]) {
+ assertThat(toString, containsString(Arrays.toString((Object[]) value)));
+ } else if (value != null && value.getClass().isArray()) {
+ // Primitive array, uses multiple specialized Arrays.toString overloads
+ } else {
+ assertThat(toString, containsString("" + value));
+ }
+ });
+ }
+
+ @Test
+ public void testBuilder_propagatesValuesToInstance() {
+ assertEquals(43, newBuilder().setNum(43).build().getNum());
+ }
+
+ @Test
+ public void testPluralFields_canHaveCustomSingularBuilderName() {
+ newBuilder().addLinkAddress(new LinkAddress("127.0.0.1/24"));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBuilder_usableOnlyOnce() {
+ SampleDataClass.Builder builder = newBuilder();
+ builder.build();
+ builder.build();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBuilder_performsValidation() {
+ newInvalidBuilder().build();
+ }
+
+ @Test
+ public void testIntDefs_haveCorrectToString() {
+ int flagsAsInt = SampleDataClass.FLAG_MANUAL_REQUEST
+ | SampleDataClass.FLAG_COMPATIBILITY_MODE_REQUEST;
+ String flagsAsString = SampleDataClass.requestFlagsToString(flagsAsInt);
+
+ assertThat(flagsAsString, containsString("MANUAL_REQUEST"));
+ assertThat(flagsAsString, containsString("COMPATIBILITY_MODE_REQUEST"));
+ assertThat(flagsAsString, not(containsString("1")));
+ assertThat(flagsAsString, not(containsString("" + flagsAsInt)));
+
+ String dataclassToString = newBuilder()
+ .setFlags(flagsAsInt)
+ .setState(SampleDataClass.STATE_UNDEFINED)
+ .build()
+ .toString();
+ assertThat(dataclassToString, containsString(flagsAsString));
+ assertThat(dataclassToString, containsString("UNDEFINED"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFlags_getValidated() {
+ newBuilder().setFlags(12345).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIntEnums_getValidated() {
+ newBuilder().setState(12345).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testStringEnums_getValidated() {
+ newBuilder().setStateName("foo").build();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCustomValidation_isTriggered() {
+ newBuilder().setNum2(-1).setNum4(1).build();
+ }
+
+ @Test
+ public void testLazyInit_isLazilyCalledOnce() {
+ assertNull(mSpecimen.mTmpStorage);
+
+ int[] tmpStorage = mSpecimen.getTmpStorage();
+ assertNotNull(tmpStorage);
+ assertSame(tmpStorage, mSpecimen.mTmpStorage);
+
+ int[] tmpStorageAgain = mSpecimen.getTmpStorage();
+ assertSame(tmpStorage, tmpStorageAgain);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCustomAnnotationValidation_isRun() {
+ newBuilder().setDayOfWeek(42).build();
+ }
+
+ private static <T extends Parcelable> T parcelAndUnparcel(
+ T original, Parcelable.Creator<T> creator) {
+ Parcel p = Parcel.obtain();
+ try {
+ original.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ return creator.createFromParcel(p);
+ } finally {
+ p.recycle();
+ }
+ }
+}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
new file mode 100644
index 000000000000..86f37fe1057e
--- /dev/null
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -0,0 +1,186 @@
+/*
+ * 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 com.android.codegentest;
+
+import android.annotation.NonNull;
+import android.os.SystemClock;
+
+import com.android.internal.util.DataClass;
+
+import java.util.concurrent.TimeUnit;
+
+@DataClass(genBuilder = true)
+public class SampleWithCustomBuilder {
+
+ long delayAmount = 0;
+ @NonNull
+ TimeUnit delayUnit = TimeUnit.MILLISECONDS;
+
+ long creationTimestamp = SystemClock.uptimeMillis();
+
+ /**
+ * You can declare a class named {@code BaseBuilder} to have the generated builder extend from
+ * it instead.
+ *
+ * Same rules apply where defining a non-abstract method will suppress the generation of a
+ * method with the same signature.
+ *
+ * For abstract generatable methods, implementations are generated as normal, but original
+ * visibility is used, allowing you to hide methods.
+ *
+ * Here for example, we hide {@link #setDelayUnit} and {@link #setDelayAmount} from public API,
+ * replacing it with {@link #setDelay} instead.
+ */
+ // Suppress setter generation for a field that is not supposed to come from user input.
+ @DataClass.Suppress("setCreationTimestamp")
+ static abstract class BaseBuilder {
+
+ /**
+ * Hide methods by declaring them with reduced (package-private) visibility.
+ */
+ abstract Builder setDelayAmount(long value);
+
+ /**
+ * Alternatively, hide methods by using @hide, to hide them from public API only.
+ *
+ * @hide
+ */
+ public abstract Builder setDelayUnit(TimeUnit value);
+
+ /**
+ * Can provide additional method on the builder, e.g. as a replacement for the ones we've
+ * just hidden.
+ */
+ public Builder setDelay(long amount, TimeUnit unit) {
+ setDelayAmount(amount);
+ setDelayUnit(unit);
+ return (Builder) this;
+ }
+ }
+
+
+
+ // Code below generated by codegen v1.0.1.
+ //
+ // DO NOT MODIFY!
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+ //
+ // CHECKSTYLE:OFF Generated code
+
+ @DataClass.Generated.Member
+ /* package-private */ SampleWithCustomBuilder(
+ long delayAmount,
+ @NonNull TimeUnit delayUnit,
+ long creationTimestamp) {
+ this.delayAmount = delayAmount;
+ this.delayUnit = delayUnit;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, delayUnit);
+ this.creationTimestamp = creationTimestamp;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public long getDelayAmount() {
+ return delayAmount;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull TimeUnit getDelayUnit() {
+ return delayUnit;
+ }
+
+ @DataClass.Generated.Member
+ public long getCreationTimestamp() {
+ return creationTimestamp;
+ }
+
+ /**
+ * A builder for {@link SampleWithCustomBuilder}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static class Builder extends BaseBuilder {
+
+ private long delayAmount;
+ private @NonNull TimeUnit delayUnit;
+ private long creationTimestamp;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ @DataClass.Generated.Member
+ @Override
+ @NonNull Builder setDelayAmount(long value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ delayAmount = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ @Override
+ public @NonNull Builder setDelayUnit(@NonNull TimeUnit value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ delayUnit = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public SampleWithCustomBuilder build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ delayAmount = 0;
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ delayUnit = TimeUnit.MILLISECONDS;
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ creationTimestamp = SystemClock.uptimeMillis();
+ }
+ SampleWithCustomBuilder o = new SampleWithCustomBuilder(
+ delayAmount,
+ delayUnit,
+ creationTimestamp);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x8) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1568235366386L,
+ codegenVersion = "1.0.1",
+ sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java",
+ inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nclass SampleWithCustomBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
+ @Deprecated
+ private void __metadata() {}
+
+}
diff --git a/tests/FeatureSplit/feature1/Android.bp b/tests/FeatureSplit/feature1/Android.bp
index 1a93e842cec5..706a4f544393 100644
--- a/tests/FeatureSplit/feature1/Android.bp
+++ b/tests/FeatureSplit/feature1/Android.bp
@@ -18,7 +18,7 @@ android_test {
name: "FeatureSplit1",
srcs: ["**/*.java"],
sdk_version: "current",
- libs: ["FeatureSplitBase"],
+ libs: ["FeatureSplitBase", "FeatureSplit2"],
aaptflags: [
"--package-id",
"0x80",
diff --git a/tests/FeatureSplit/feature1/AndroidManifest.xml b/tests/FeatureSplit/feature1/AndroidManifest.xml
index b87361faac62..086c2c33422d 100644
--- a/tests/FeatureSplit/feature1/AndroidManifest.xml
+++ b/tests/FeatureSplit/feature1/AndroidManifest.xml
@@ -19,6 +19,7 @@
featureSplit="feature1">
<uses-sdk android:minSdkVersion="21" />
+ <uses-split android:name="feature2" />
<application>
<activity android:name=".one.One" android:label="Feature One">
diff --git a/tests/FeatureSplit/feature1/res/layout/included.xml b/tests/FeatureSplit/feature1/res/layout/included.xml
index c64bdb7ff85f..f0c56f872438 100644
--- a/tests/FeatureSplit/feature1/res/layout/included.xml
+++ b/tests/FeatureSplit/feature1/res/layout/included.xml
@@ -2,4 +2,5 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text"
android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ android:text="@string/feature2_string" />
diff --git a/tests/FeatureSplit/feature1/res/values/values.xml b/tests/FeatureSplit/feature1/res/values/values.xml
index 7d58865b546e..6a840df2c79c 100644
--- a/tests/FeatureSplit/feature1/res/values/values.xml
+++ b/tests/FeatureSplit/feature1/res/values/values.xml
@@ -20,7 +20,8 @@
<integer name="test_integer2">200</integer>
<color name="test_color2">#00ff00</color>
<string-array name="string_array2">
- <item>@string/app_title</item>
+ <item>@string/app_title</item>
+ <item>@string/feature2_string</item>
</string-array>
</resources>
diff --git a/tests/FeatureSplit/feature2/res/values/values.xml b/tests/FeatureSplit/feature2/res/values/values.xml
index af5ed1b79b26..70e772c0d019 100644
--- a/tests/FeatureSplit/feature2/res/values/values.xml
+++ b/tests/FeatureSplit/feature2/res/values/values.xml
@@ -15,10 +15,11 @@
-->
<resources>
+ <string name="feature2_string">feature 2 string referenced from feature 1</string>
<integer name="test_integer3">300</integer>
<color name="test_color3">#0000ff</color>
<string-array name="string_array3">
- <item>@string/app_title</item>
+ <item>@string/app_title</item>
</string-array>
</resources>
diff --git a/tests/GamePerformance/AndroidManifest.xml b/tests/GamePerformance/AndroidManifest.xml
index b331e2c07e14..2ff7fa65664e 100644
--- a/tests/GamePerformance/AndroidManifest.xml
+++ b/tests/GamePerformance/AndroidManifest.xml
@@ -16,7 +16,9 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.gameperformance">
+ package="android.gameperformance"
+ android:versionCode="3"
+ android:versionName="3.0" >
<uses-sdk android:minSdkVersion="25"/>
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
@@ -24,7 +26,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:theme="@style/noeffects">
<uses-library android:name="android.test.runner" />
- <activity android:name="android.gameperformance.GamePerformanceActivity" >
+ <activity android:name="android.gameperformance.GamePerformanceActivity"
+ android:screenOrientation="landscape" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
diff --git a/tests/GamePerformance/res/drawable/animation.xml b/tests/GamePerformance/res/drawable/animation.xml
new file mode 100644
index 000000000000..b423ff0d1ee4
--- /dev/null
+++ b/tests/GamePerformance/res/drawable/animation.xml
@@ -0,0 +1,29 @@
+<!--
+ * 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.
+ -->
+
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/animation" android:oneshot="false">
+ <item android:drawable="@drawable/digit_0" android:duration="15" />
+ <item android:drawable="@drawable/digit_1" android:duration="15" />
+ <item android:drawable="@drawable/digit_2" android:duration="15" />
+ <item android:drawable="@drawable/digit_3" android:duration="15" />
+ <item android:drawable="@drawable/digit_4" android:duration="15" />
+ <item android:drawable="@drawable/digit_5" android:duration="15" />
+ <item android:drawable="@drawable/digit_6" android:duration="15" />
+ <item android:drawable="@drawable/digit_7" android:duration="15" />
+ <item android:drawable="@drawable/digit_8" android:duration="15" />
+ <item android:drawable="@drawable/digit_9" android:duration="15" />
+ </animation-list> \ No newline at end of file
diff --git a/tests/GamePerformance/res/drawable/digit_0.png b/tests/GamePerformance/res/drawable/digit_0.png
new file mode 100644
index 000000000000..7264e3ee3771
--- /dev/null
+++ b/tests/GamePerformance/res/drawable/digit_0.png
Binary files differ
diff --git a/tests/GamePerformance/res/drawable/digit_1.png b/tests/GamePerformance/res/drawable/digit_1.png
new file mode 100644
index 000000000000..f098a71a4ab4
--- /dev/null
+++ b/tests/GamePerformance/res/drawable/digit_1.png
Binary files differ
diff --git a/tests/GamePerformance/res/drawable/digit_2.png b/tests/GamePerformance/res/drawable/digit_2.png
new file mode 100644
index 000000000000..f08cd31b4118
--- /dev/null
+++ b/tests/GamePerformance/res/drawable/digit_2.png
Binary files differ
diff --git a/tests/GamePerformance/res/drawable/digit_3.png b/tests/GamePerformance/res/drawable/digit_3.png
new file mode 100644
index 000000000000..497df8a9f473
--- /dev/null
+++ b/tests/GamePerformance/res/drawable/digit_3.png
Binary files differ
diff --git a/tests/GamePerformance/res/drawable/digit_4.png b/tests/GamePerformance/res/drawable/digit_4.png
new file mode 100644
index 000000000000..10efe8cf11b2
--- /dev/null
+++ b/tests/GamePerformance/res/drawable/digit_4.png
Binary files differ
diff --git a/tests/GamePerformance/res/drawable/digit_5.png b/tests/GamePerformance/res/drawable/digit_5.png
new file mode 100644
index 000000000000..1018a2fad733
--- /dev/null
+++ b/tests/GamePerformance/res/drawable/digit_5.png
Binary files differ
diff --git a/tests/GamePerformance/res/drawable/digit_6.png b/tests/GamePerformance/res/drawable/digit_6.png
new file mode 100644
index 000000000000..593c467d1529
--- /dev/null
+++ b/tests/GamePerformance/res/drawable/digit_6.png
Binary files differ
diff --git a/tests/GamePerformance/res/drawable/digit_7.png b/tests/GamePerformance/res/drawable/digit_7.png
new file mode 100644
index 000000000000..041b95fd8748
--- /dev/null
+++ b/tests/GamePerformance/res/drawable/digit_7.png
Binary files differ
diff --git a/tests/GamePerformance/res/drawable/digit_8.png b/tests/GamePerformance/res/drawable/digit_8.png
new file mode 100644
index 000000000000..f8fa4969cb28
--- /dev/null
+++ b/tests/GamePerformance/res/drawable/digit_8.png
Binary files differ
diff --git a/tests/GamePerformance/res/drawable/digit_9.png b/tests/GamePerformance/res/drawable/digit_9.png
new file mode 100644
index 000000000000..303b1da3d3f8
--- /dev/null
+++ b/tests/GamePerformance/res/drawable/digit_9.png
Binary files differ
diff --git a/tests/GamePerformance/res/drawable/logo.png b/tests/GamePerformance/res/drawable/logo.png
new file mode 100644
index 000000000000..61391df52077
--- /dev/null
+++ b/tests/GamePerformance/res/drawable/logo.png
Binary files differ
diff --git a/tests/GamePerformance/src/android/gameperformance/BaseTest.java b/tests/GamePerformance/src/android/gameperformance/BaseTest.java
new file mode 100644
index 000000000000..b0640b444914
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/BaseTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.gameperformance;
+
+import java.text.DecimalFormat;
+import java.util.concurrent.TimeUnit;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.util.Log;
+import android.view.WindowManager;
+
+/**
+ * Base class for a test that performs bisection to determine maximum
+ * performance of a metric test measures.
+ */
+public abstract class BaseTest {
+ private final static String TAG = "BaseTest";
+
+ // Time to wait for render warm up. No statistics is collected during this pass.
+ private final static long WARM_UP_TIME = TimeUnit.SECONDS.toMillis(5);
+
+ // Perform pass to probe the configuration using iterations. After each iteration current FPS is
+ // checked and if it looks obviously bad, pass gets stopped earlier. Once all iterations are
+ // done and final FPS is above PASS_THRESHOLD pass to probe is considered successful.
+ private final static long TEST_ITERATION_TIME = TimeUnit.SECONDS.toMillis(12);
+ private final static int TEST_ITERATION_COUNT = 5;
+
+ // FPS pass test threshold, in ratio from ideal FPS, that matches device
+ // refresh rate.
+ private final static double PASS_THRESHOLD = 0.95;
+ // FPS threshold, in ratio from ideal FPS, to identify that current pass to probe is obviously
+ // bad and to stop pass earlier.
+ private final static double OBVIOUS_BAD_THRESHOLD = 0.90;
+
+ private static DecimalFormat DOUBLE_FORMATTER = new DecimalFormat("#.##");
+
+ private final GamePerformanceActivity mActivity;
+
+ // Device's refresh rate.
+ private final double mRefreshRate;
+
+ public BaseTest(@NonNull GamePerformanceActivity activity) {
+ mActivity = activity;
+ final WindowManager windowManager =
+ (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
+ mRefreshRate = windowManager.getDefaultDisplay().getRefreshRate();
+ }
+
+ @NonNull
+ public Context getContext() {
+ return mActivity;
+ }
+
+ @NonNull
+ public GamePerformanceActivity getActivity() {
+ return mActivity;
+ }
+
+ // Returns name of the test.
+ public abstract String getName();
+
+ // Returns unit name.
+ public abstract String getUnitName();
+
+ // Returns number of measured units per one bisection unit.
+ public abstract double getUnitScale();
+
+ // Initializes test.
+ public abstract void initUnits(double unitCount);
+
+ // Initializes probe pass.
+ protected abstract void initProbePass(int probe);
+
+ // Frees probe pass.
+ protected abstract void freeProbePass();
+
+ /**
+ * Performs the test and returns maximum number of measured units achieved. Unit is test
+ * specific and name is returned by getUnitName. Returns 0 in case of failure.
+ */
+ public double run() {
+ try {
+ Log.i(TAG, "Test started " + getName());
+
+ final double passFps = PASS_THRESHOLD * mRefreshRate;
+ final double obviousBadFps = OBVIOUS_BAD_THRESHOLD * mRefreshRate;
+
+ // Bisection bounds. Probe value is taken as middle point. Then it used to initialize
+ // test with probe * getUnitScale units. In case probe passed, lowLimit is updated to
+ // probe, otherwise upLimit is updated to probe. lowLimit contains probe that passes
+ // and upLimit contains the probe that fails. Each iteration narrows the range.
+ // Iterations continue until range is collapsed and lowLimit contains actual test
+ // result.
+ int lowLimit = 0; // Initially 0, that is recognized as failure.
+ int upLimit = 250;
+
+ while (true) {
+ int probe = (lowLimit + upLimit) / 2;
+ if (probe == lowLimit) {
+ Log.i(TAG, "Test done: " + DOUBLE_FORMATTER.format(probe * getUnitScale()) +
+ " " + getUnitName());
+ return probe * getUnitScale();
+ }
+
+ Log.i(TAG, "Start probe: " + DOUBLE_FORMATTER.format(probe * getUnitScale()) + " " +
+ getUnitName());
+ initProbePass(probe);
+
+ Thread.sleep(WARM_UP_TIME);
+
+ getActivity().resetFrameTimes();
+
+ double fps = 0.0f;
+ for (int i = 0; i < TEST_ITERATION_COUNT; ++i) {
+ Thread.sleep(TEST_ITERATION_TIME);
+ fps = getActivity().getFps();
+ if (fps < obviousBadFps) {
+ // Stop test earlier, we could not fit the loading.
+ break;
+ }
+ }
+
+ freeProbePass();
+
+ Log.i(TAG, "Finish probe: " + DOUBLE_FORMATTER.format(probe * getUnitScale()) +
+ " " + getUnitName() + " - " + DOUBLE_FORMATTER.format(fps) + " FPS.");
+ if (fps < passFps) {
+ upLimit = probe;
+ } else {
+ lowLimit = probe;
+ }
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/GamePerformance/src/android/gameperformance/CPULoadThread.java b/tests/GamePerformance/src/android/gameperformance/CPULoadThread.java
new file mode 100644
index 000000000000..fa6f03bf88cf
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/CPULoadThread.java
@@ -0,0 +1,61 @@
+/*
+ * 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.gameperformance;
+
+/**
+ * Ballast thread that emulates CPU load by performing heavy computation in loop.
+ */
+public class CPULoadThread extends Thread {
+ private boolean mStopRequest;
+
+ public CPULoadThread() {
+ mStopRequest = false;
+ }
+
+ private static double computePi() {
+ double accumulator = 0;
+ double prevAccumulator = -1;
+ int index = 1;
+ while (true) {
+ accumulator += ((1.0 / (2.0 * index - 1)) - (1.0 / (2.0 * index + 1)));
+ if (accumulator == prevAccumulator) {
+ break;
+ }
+ prevAccumulator = accumulator;
+ index += 2;
+ }
+ return 4 * accumulator;
+ }
+
+ // Requests thread to stop.
+ public void issueStopRequest() {
+ synchronized (this) {
+ mStopRequest = true;
+ }
+ }
+
+ @Override
+ public void run() {
+ // Load CPU by PI computation.
+ while (computePi() != 0) {
+ synchronized (this) {
+ if (mStopRequest) {
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/tests/GamePerformance/src/android/gameperformance/ControlsTest.java b/tests/GamePerformance/src/android/gameperformance/ControlsTest.java
new file mode 100644
index 000000000000..6c36ddcc620d
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/ControlsTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.gameperformance;
+
+import android.annotation.NonNull;
+
+/**
+ * Tests that verifies how many UI controls can be handled to keep FPS close to device refresh rate.
+ * As a test UI control ImageView with an infinite animation is chosen. The animation has refresh
+ * rate ~67Hz that forces all devices to refresh UI at highest possible rate.
+ */
+public class ControlsTest extends BaseTest {
+ public ControlsTest(@NonNull GamePerformanceActivity activity) {
+ super(activity);
+ }
+
+ @NonNull
+ public CustomControlView getView() {
+ return getActivity().getControlView();
+ }
+
+ @Override
+ protected void initProbePass(int probe) {
+ try {
+ getActivity().attachControlView();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return;
+ }
+ initUnits(probe * getUnitScale());
+ }
+
+ @Override
+ protected void freeProbePass() {
+ }
+
+ @Override
+ public String getName() {
+ return "control_count";
+ }
+
+ @Override
+ public String getUnitName() {
+ return "controls";
+ }
+
+ @Override
+ public double getUnitScale() {
+ return 5.0;
+ }
+
+ @Override
+ public void initUnits(double controlCount) {
+ try {
+ getView().createControls(getActivity(), (int)Math.round(controlCount));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/GamePerformance/src/android/gameperformance/CustomControlView.java b/tests/GamePerformance/src/android/gameperformance/CustomControlView.java
new file mode 100644
index 000000000000..219085a83b2c
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/CustomControlView.java
@@ -0,0 +1,128 @@
+/*
+ * 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.gameperformance;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.AnimationDrawable;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.AbsoluteLayout;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+/**
+ * View that holds requested number of UI controls as ImageView with an infinite animation.
+ */
+public class CustomControlView extends AbsoluteLayout {
+ private final static int CONTROL_DIMENTION = 48;
+
+ private final int mPerRowControlCount;
+ private List<Long> mFrameTimes = new ArrayList<>();
+
+ public CustomControlView(@NonNull Context context) {
+ super(context);
+
+ final WindowManager windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ mPerRowControlCount = windowManager.getDefaultDisplay().getWidth() / CONTROL_DIMENTION;
+ }
+
+ /**
+ * Helper class that overrides ImageView and observes draw requests. Only
+ * one such control is created which is the first control in the view.
+ */
+ class ReferenceImageView extends ImageView {
+ public ReferenceImageView(Context context) {
+ super(context);
+ }
+ @Override
+ public void draw(Canvas canvas) {
+ reportFrame();
+ super.draw(canvas);
+ }
+ }
+
+ @WorkerThread
+ public void createControls(
+ @NonNull Activity activity, int controlCount) throws InterruptedException {
+ synchronized (this) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ removeAllViews();
+
+ for (int i = 0; i < controlCount; ++i) {
+ final ImageView image = (i == 0) ?
+ new ReferenceImageView(activity) : new ImageView(activity);
+ final int x = (i % mPerRowControlCount) * CONTROL_DIMENTION;
+ final int y = (i / mPerRowControlCount) * CONTROL_DIMENTION;
+ final AbsoluteLayout.LayoutParams layoutParams =
+ new AbsoluteLayout.LayoutParams(
+ CONTROL_DIMENTION, CONTROL_DIMENTION, x, y);
+ image.setLayoutParams(layoutParams);
+ image.setBackgroundResource(R.drawable.animation);
+ final AnimationDrawable animation =
+ (AnimationDrawable)image.getBackground();
+ animation.start();
+ addView(image);
+ }
+
+ latch.countDown();
+ }
+ });
+ latch.await();
+ }
+ }
+
+ @MainThread
+ private void reportFrame() {
+ final long time = System.currentTimeMillis();
+ synchronized (mFrameTimes) {
+ mFrameTimes.add(time);
+ }
+ }
+
+ /**
+ * Resets frame times in order to calculate FPS for the different test pass.
+ */
+ public void resetFrameTimes() {
+ synchronized (mFrameTimes) {
+ mFrameTimes.clear();
+ }
+ }
+
+ /**
+ * Returns current FPS based on collected frame times.
+ */
+ public double getFps() {
+ synchronized (mFrameTimes) {
+ if (mFrameTimes.size() < 2) {
+ return 0.0f;
+ }
+ return 1000.0 * mFrameTimes.size() /
+ (mFrameTimes.get(mFrameTimes.size() - 1) - mFrameTimes.get(0));
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java b/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java
index 2b37280ae9b5..08697ae95376 100644
--- a/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java
+++ b/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java
@@ -17,23 +17,36 @@ package android.gameperformance;
import java.util.ArrayList;
import java.util.List;
-import java.util.Random;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
+import android.util.Log;
public class CustomOpenGLView extends GLSurfaceView {
- private Random mRandom;
- private List<Long> mFrameTimes;
+ public final static String TAG = "CustomOpenGLView";
- public CustomOpenGLView(Context context) {
+ private final List<Long> mFrameTimes;
+ private final Object mLock = new Object();
+ private boolean mRenderReady = false;
+ private FrameDrawer mFrameDrawer = null;
+
+ private float mRenderRatio;
+ private int mRenderWidth;
+ private int mRenderHeight;
+
+ public interface FrameDrawer {
+ public void drawFrame(@NonNull GL10 gl);
+ }
+
+ public CustomOpenGLView(@NonNull Context context) {
super(context);
- mRandom = new Random();
mFrameTimes = new ArrayList<Long>();
setEGLContextClientVersion(2);
@@ -41,25 +54,35 @@ public class CustomOpenGLView extends GLSurfaceView {
setRenderer(new GLSurfaceView.Renderer() {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ Log.i(TAG, "SurfaceCreated: " + config);
GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
gl.glClearDepthf(1.0f);
- gl.glEnable(GL10.GL_DEPTH_TEST);
+ gl.glDisable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
- GL10.GL_NICEST); }
+ GL10.GL_NICEST);
+ synchronized (mLock) {
+ mRenderReady = true;
+ mLock.notify();
+ }
+ }
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
+ Log.i(TAG, "SurfaceChanged: " + width + "x" + height);
GLES20.glViewport(0, 0, width, height);
+ setRenderBounds(width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
- GLES20.glClearColor(
- mRandom.nextFloat(), mRandom.nextFloat(), mRandom.nextFloat(), 1.0f);
+ GLES20.glClearColor(0.25f, 0.25f, 0.25f, 1.0f);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
- synchronized (mFrameTimes) {
+ synchronized (mLock) {
+ if (mFrameDrawer != null) {
+ mFrameDrawer.drawFrame(gl);
+ }
mFrameTimes.add(System.currentTimeMillis());
}
}
@@ -67,20 +90,38 @@ public class CustomOpenGLView extends GLSurfaceView {
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
+ public void setRenderBounds(int width, int height) {
+ mRenderWidth = width;
+ mRenderHeight = height;
+ mRenderRatio = (float) mRenderWidth / mRenderHeight;
+ }
+
+ public float getRenderRatio() {
+ return mRenderRatio;
+ }
+
+ public int getRenderWidth() {
+ return mRenderWidth;
+ }
+
+ public int getRenderHeight() {
+ return mRenderHeight;
+ }
+
/**
- * Resets frame times in order to calculate fps for different test pass.
+ * Resets frame times in order to calculate FPS for the different test pass.
*/
public void resetFrameTimes() {
- synchronized (mFrameTimes) {
+ synchronized (mLock) {
mFrameTimes.clear();
}
}
/**
- * Returns current fps based on collected frame times.
+ * Returns current FPS based on collected frame times.
*/
public double getFps() {
- synchronized (mFrameTimes) {
+ synchronized (mLock) {
if (mFrameTimes.size() < 2) {
return 0.0f;
}
@@ -88,4 +129,26 @@ public class CustomOpenGLView extends GLSurfaceView {
(mFrameTimes.get(mFrameTimes.size() - 1) - mFrameTimes.get(0));
}
}
+
+ /**
+ * Waits for render attached to the view.
+ */
+ public void waitRenderReady() {
+ synchronized (mLock) {
+ while (!mRenderReady) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets/resets frame drawer.
+ */
+ public void setFrameDrawer(@Nullable FrameDrawer frameDrawer) {
+ mFrameDrawer = frameDrawer;
+ }
}
diff --git a/tests/GamePerformance/src/android/gameperformance/DeviceCallsOpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/DeviceCallsOpenGLTest.java
new file mode 100644
index 000000000000..df2ae5cf670a
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/DeviceCallsOpenGLTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.gameperformance;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.annotation.NonNull;
+
+/**
+ * Tests that verifies maximum number of device calls to render the geometry to keep FPS close to
+ * the device refresh rate. This uses trivial one triangle patch that is rendered multiple times.
+ */
+public class DeviceCallsOpenGLTest extends RenderPatchOpenGLTest {
+
+ public DeviceCallsOpenGLTest(@NonNull GamePerformanceActivity activity) {
+ super(activity);
+ }
+
+ @Override
+ public String getName() {
+ return "device_calls";
+ }
+
+ @Override
+ public String getUnitName() {
+ return "calls";
+ }
+
+ @Override
+ public double getUnitScale() {
+ return 25.0;
+ }
+
+ @Override
+ public void initUnits(double deviceCallsD) {
+ final List<RenderPatchAnimation> renderPatches = new ArrayList<>();
+ final RenderPatch renderPatch = new RenderPatch(1 /* triangleCount */,
+ 0.05f /* dimension */,
+ RenderPatch.TESSELLATION_BASE);
+ final int deviceCalls = (int)Math.round(deviceCallsD);
+ for (int i = 0; i < deviceCalls; ++i) {
+ renderPatches.add(new RenderPatchAnimation(renderPatch, getView().getRenderRatio()));
+ }
+ setRenderPatches(renderPatches);
+ }
+} \ No newline at end of file
diff --git a/tests/GamePerformance/src/android/gameperformance/FillRateOpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/FillRateOpenGLTest.java
new file mode 100644
index 000000000000..9b2619372d16
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/FillRateOpenGLTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.gameperformance;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.microedition.khronos.opengles.GL;
+
+import android.annotation.NonNull;
+import android.opengl.GLES20;
+
+/**
+ * Tests that verifies maximum fill rate per frame can be used to keep FPS close to the device
+ * refresh rate. It works in two modes, blend disabled and blend enabled. This uses few big simple
+ * quad patches.
+ */
+public class FillRateOpenGLTest extends RenderPatchOpenGLTest {
+ private final float[] BLEND_COLOR = new float[] { 1.0f, 1.0f, 1.0f, 0.2f };
+
+ private final boolean mTestBlend;
+
+ public FillRateOpenGLTest(@NonNull GamePerformanceActivity activity, boolean testBlend) {
+ super(activity);
+ mTestBlend = testBlend;
+ }
+
+ @Override
+ public String getName() {
+ return mTestBlend ? "blend_rate" : "fill_rate";
+ }
+
+ @Override
+ public String getUnitName() {
+ return "screens";
+ }
+
+ @Override
+ public double getUnitScale() {
+ return 0.2;
+ }
+
+ @Override
+ public void initUnits(double screens) {
+ final CustomOpenGLView view = getView();
+ final int pixelRate = (int)Math.round(screens * view.getHeight() * view.getWidth());
+ final int maxPerPath = view.getHeight() * view.getHeight();
+
+ final int patchCount = (int)(pixelRate + maxPerPath -1) / maxPerPath;
+ final float patchDimension =
+ (float)((Math.sqrt(2.0f) * pixelRate / patchCount) / maxPerPath);
+
+ final List<RenderPatchAnimation> renderPatches = new ArrayList<>();
+ final RenderPatch renderPatch = new RenderPatch(2 /* triangleCount for quad */,
+ patchDimension,
+ RenderPatch.TESSELLATION_BASE);
+ for (int i = 0; i < patchCount; ++i) {
+ renderPatches.add(new RenderPatchAnimation(renderPatch, getView().getRenderRatio()));
+ }
+ setRenderPatches(renderPatches);
+ }
+
+ @Override
+ public float[] getColor() {
+ return BLEND_COLOR;
+ }
+
+ @Override
+ public void onBeforeDraw(GL gl) {
+ if (!mTestBlend) {
+ return;
+ }
+
+ // Enable blend if needed.
+ GLES20.glEnable(GLES20.GL_BLEND);
+ OpenGLUtils.checkGlError("disableBlend");
+ GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+ OpenGLUtils.checkGlError("blendFunction");
+ }
+} \ No newline at end of file
diff --git a/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java b/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java
index b0e6196b53d7..dc745f17e698 100644
--- a/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java
+++ b/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java
@@ -25,14 +25,32 @@ import android.view.WindowManager;
import android.widget.RelativeLayout;
/**
- * Minimal activity that holds SurfaceView or GLSurfaceView.
- * call attachSurfaceView or attachOpenGLView to switch views.
+ * Minimal activity that holds different types of views.
+ * call attachSurfaceView, attachOpenGLView or attachControlView to switch
+ * the view.
*/
public class GamePerformanceActivity extends Activity {
private CustomSurfaceView mSurfaceView = null;
private CustomOpenGLView mOpenGLView = null;
+ private CustomControlView mControlView = null;
+
private RelativeLayout mRootLayout;
+ private void detachAllViews() {
+ if (mOpenGLView != null) {
+ mRootLayout.removeView(mOpenGLView);
+ mOpenGLView = null;
+ }
+ if (mSurfaceView != null) {
+ mRootLayout.removeView(mSurfaceView);
+ mSurfaceView = null;
+ }
+ if (mControlView != null) {
+ mRootLayout.removeView(mControlView);
+ mControlView = null;
+ }
+ }
+
public void attachSurfaceView() throws InterruptedException {
synchronized (mRootLayout) {
if (mSurfaceView != null) {
@@ -42,10 +60,7 @@ public class GamePerformanceActivity extends Activity {
runOnUiThread(new Runnable() {
@Override
public void run() {
- if (mOpenGLView != null) {
- mRootLayout.removeView(mOpenGLView);
- mOpenGLView = null;
- }
+ detachAllViews();
mSurfaceView = new CustomSurfaceView(GamePerformanceActivity.this);
mRootLayout.addView(mSurfaceView);
latch.countDown();
@@ -65,10 +80,7 @@ public class GamePerformanceActivity extends Activity {
runOnUiThread(new Runnable() {
@Override
public void run() {
- if (mSurfaceView != null) {
- mRootLayout.removeView(mSurfaceView);
- mSurfaceView = null;
- }
+ detachAllViews();
mOpenGLView = new CustomOpenGLView(GamePerformanceActivity.this);
mRootLayout.addView(mOpenGLView);
latch.countDown();
@@ -78,6 +90,40 @@ public class GamePerformanceActivity extends Activity {
}
}
+ public void attachControlView() throws InterruptedException {
+ synchronized (mRootLayout) {
+ if (mControlView != null) {
+ return;
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ detachAllViews();
+ mControlView = new CustomControlView(GamePerformanceActivity.this);
+ mRootLayout.addView(mControlView);
+ latch.countDown();
+ }
+ });
+ latch.await();
+ }
+ }
+
+
+ public CustomOpenGLView getOpenGLView() {
+ if (mOpenGLView == null) {
+ throw new RuntimeException("OpenGL view is not attached");
+ }
+ return mOpenGLView;
+ }
+
+ public CustomControlView getControlView() {
+ if (mControlView == null) {
+ throw new RuntimeException("Control view is not attached");
+ }
+ return mControlView;
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -105,6 +151,8 @@ public class GamePerformanceActivity extends Activity {
mSurfaceView.resetFrameTimes();
} else if (mOpenGLView != null) {
mOpenGLView.resetFrameTimes();
+ } else if (mControlView != null) {
+ mControlView.resetFrameTimes();
} else {
throw new IllegalStateException("Nothing attached");
}
@@ -115,6 +163,8 @@ public class GamePerformanceActivity extends Activity {
return mSurfaceView.getFps();
} else if (mOpenGLView != null) {
return mOpenGLView.getFps();
+ } else if (mControlView != null) {
+ return mControlView.getFps();
} else {
throw new IllegalStateException("Nothing attached");
}
diff --git a/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java b/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java
index e5de7d75886e..d6e2861c03a7 100644
--- a/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java
+++ b/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java
@@ -17,14 +17,18 @@ package android.gameperformance;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
+import android.annotation.NonNull;
import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Trace;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
@@ -84,4 +88,50 @@ public class GamePerformanceTest extends
getInstrumentation().sendStatus(Activity.RESULT_OK, status);
}
+
+ @SmallTest
+ public void testPerformanceMetricsWithoutExtraLoad() throws IOException, InterruptedException {
+ final Bundle status = runPerformanceTests("no_extra_load_");
+ getInstrumentation().sendStatus(Activity.RESULT_OK, status);
+ }
+
+ @SmallTest
+ public void testPerformanceMetricsWithExtraLoad() throws IOException, InterruptedException {
+ // Start CPU ballast threads first.
+ CPULoadThread[] cpuLoadThreads = new CPULoadThread[2];
+ for (int i = 0; i < cpuLoadThreads.length; ++i) {
+ cpuLoadThreads[i] = new CPULoadThread();
+ cpuLoadThreads[i].start();
+ }
+
+ final Bundle status = runPerformanceTests("extra_load_");
+
+ for (int i = 0; i < cpuLoadThreads.length; ++i) {
+ cpuLoadThreads[i].issueStopRequest();
+ cpuLoadThreads[i].join();
+ }
+
+ getInstrumentation().sendStatus(Activity.RESULT_OK, status);
+ }
+
+ @NonNull
+ private Bundle runPerformanceTests(@NonNull String prefix) {
+ final Bundle status = new Bundle();
+
+ final GamePerformanceActivity activity = getActivity();
+
+ final List<BaseTest> tests = new ArrayList<>();
+ tests.add(new TriangleCountOpenGLTest(activity));
+ tests.add(new FillRateOpenGLTest(activity, false /* testBlend */));
+ tests.add(new FillRateOpenGLTest(activity, true /* testBlend */));
+ tests.add(new DeviceCallsOpenGLTest(activity));
+ tests.add(new ControlsTest(activity));
+
+ for (BaseTest test : tests) {
+ final double result = test.run();
+ status.putDouble(prefix + test.getName(), result);
+ }
+
+ return status;
+ }
}
diff --git a/tests/GamePerformance/src/android/gameperformance/OpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/OpenGLTest.java
new file mode 100644
index 000000000000..1d3f95cdd5b8
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/OpenGLTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.gameperformance;
+
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.annotation.NonNull;
+import android.gameperformance.CustomOpenGLView.FrameDrawer;
+
+/**
+ * Base class for all OpenGL based tests.
+ */
+public abstract class OpenGLTest extends BaseTest {
+ public OpenGLTest(@NonNull GamePerformanceActivity activity) {
+ super(activity);
+ }
+
+ @NonNull
+ public CustomOpenGLView getView() {
+ return getActivity().getOpenGLView();
+ }
+
+ // Performs test drawing.
+ protected abstract void draw(GL gl);
+
+ // Initializes the test on first draw call.
+ private class ParamFrameDrawer implements FrameDrawer {
+ private final double mUnitCount;
+ private boolean mInited;
+
+ public ParamFrameDrawer(double unitCount) {
+ mUnitCount = unitCount;
+ mInited = false;
+ }
+
+ @Override
+ public void drawFrame(GL10 gl) {
+ if (!mInited) {
+ initUnits(mUnitCount);
+ mInited = true;
+ }
+ draw(gl);
+ }
+ }
+
+ @Override
+ protected void initProbePass(int probe) {
+ try {
+ getActivity().attachOpenGLView();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return;
+ }
+ getView().waitRenderReady();
+ getView().setFrameDrawer(new ParamFrameDrawer(probe * getUnitScale()));
+ }
+
+ @Override
+ protected void freeProbePass() {
+ getView().setFrameDrawer(null);
+ }
+} \ No newline at end of file
diff --git a/tests/GamePerformance/src/android/gameperformance/OpenGLUtils.java b/tests/GamePerformance/src/android/gameperformance/OpenGLUtils.java
new file mode 100644
index 000000000000..4f98c52b4b39
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/OpenGLUtils.java
@@ -0,0 +1,92 @@
+/*
+ * 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.gameperformance;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.BitmapFactory;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.util.Log;
+
+/**
+ * Helper class for OpenGL.
+ */
+public class OpenGLUtils {
+ private final static String TAG = "OpenGLUtils";
+
+ public static void checkGlError(String glOperation) {
+ final int error = GLES20.glGetError();
+ if (error == GLES20.GL_NO_ERROR) {
+ return;
+ }
+ final String errorMessage = glOperation + ": glError " + error;
+ Log.e(TAG, errorMessage);
+ }
+
+ public static int loadShader(int type, String shaderCode) {
+ final int shader = GLES20.glCreateShader(type);
+ checkGlError("createShader");
+
+ GLES20.glShaderSource(shader, shaderCode);
+ checkGlError("shaderSource");
+ GLES20.glCompileShader(shader);
+ checkGlError("shaderCompile");
+
+ return shader;
+ }
+
+ public static int createProgram(@NonNull String vertexShaderCode,
+ @NonNull String fragmentShaderCode) {
+ final int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
+ final int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
+
+ final int program = GLES20.glCreateProgram();
+ checkGlError("createProgram");
+ GLES20.glAttachShader(program, vertexShader);
+ checkGlError("attachVertexShader");
+ GLES20.glAttachShader(program, fragmentShader);
+ checkGlError("attachFragmentShader");
+ GLES20.glLinkProgram(program);
+ checkGlError("linkProgram");
+
+ return program;
+ }
+
+ public static int createTexture(@NonNull Context context, int resource) {
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inScaled = false;
+
+ final int[] textureHandle = new int[1];
+ GLES20.glGenTextures(1, textureHandle, 0);
+ OpenGLUtils.checkGlError("GenTextures");
+ final int handle = textureHandle[0];
+
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, handle);
+ GLES20.glTexParameteri(
+ GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+ GLES20.glTexParameteri(
+ GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+ GLUtils.texImage2D(
+ GLES20.GL_TEXTURE_2D,
+ 0,
+ BitmapFactory.decodeResource(
+ context.getResources(), resource, options),
+ 0);
+
+ return handle;
+ }
+} \ No newline at end of file
diff --git a/tests/GamePerformance/src/android/gameperformance/RenderPatch.java b/tests/GamePerformance/src/android/gameperformance/RenderPatch.java
new file mode 100644
index 000000000000..2e69a61475db
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/RenderPatch.java
@@ -0,0 +1,150 @@
+/*
+ * 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.gameperformance;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Helper class that generates patch to render. Patch is a regular polygon with the center in 0.
+ * Regular polygon fits in circle with requested radius.
+ */
+public class RenderPatch {
+ public static final int FLOAT_SIZE = 4;
+ public static final int SHORT_SIZE = 2;
+ public static final int VERTEX_COORD_COUNT = 3;
+ public static final int VERTEX_STRIDE = VERTEX_COORD_COUNT * FLOAT_SIZE;
+ public static final int TEXTURE_COORD_COUNT = 2;
+ public static final int TEXTURE_STRIDE = TEXTURE_COORD_COUNT * FLOAT_SIZE;
+
+ // Tessellation is done using points on circle.
+ public static final int TESSELLATION_BASE = 0;
+ // Tesselation is done using extra point in 0.
+ public static final int TESSELLATION_TO_CENTER = 1;
+
+ // Radius of circle that fits polygon.
+ private final float mDimension;
+
+ private final ByteBuffer mVertexBuffer;
+ private final ByteBuffer mTextureBuffer;
+ private final ByteBuffer mIndexBuffer;
+
+ public RenderPatch(int triangleCount, float dimension, int tessellation) {
+ mDimension = dimension;
+
+ int pointCount;
+ int externalPointCount;
+
+ if (triangleCount < 1) {
+ throw new IllegalArgumentException("Too few triangles to perform tessellation");
+ }
+
+ switch (tessellation) {
+ case TESSELLATION_BASE:
+ externalPointCount = triangleCount + 2;
+ pointCount = externalPointCount;
+ break;
+ case TESSELLATION_TO_CENTER:
+ if (triangleCount < 3) {
+ throw new IllegalArgumentException(
+ "Too few triangles to perform tessellation to center");
+ }
+ externalPointCount = triangleCount;
+ pointCount = triangleCount + 1;
+ break;
+ default:
+ throw new IllegalArgumentException("Wrong tesselation requested");
+ }
+
+ if (pointCount > Short.MAX_VALUE) {
+ throw new IllegalArgumentException("Number of requested triangles is too big");
+ }
+
+ mVertexBuffer = ByteBuffer.allocateDirect(pointCount * VERTEX_STRIDE);
+ mVertexBuffer.order(ByteOrder.nativeOrder());
+
+ mTextureBuffer = ByteBuffer.allocateDirect(pointCount * TEXTURE_STRIDE);
+ mTextureBuffer.order(ByteOrder.nativeOrder());
+
+ for (int i = 0; i < externalPointCount; ++i) {
+ // Use 45 degree rotation to make quad aligned along axises in case
+ // triangleCount is four.
+ final double angle = Math.PI * 0.25 + (Math.PI * 2.0 * i) / (externalPointCount);
+ // Positions
+ mVertexBuffer.putFloat((float) (dimension * Math.sin(angle)));
+ mVertexBuffer.putFloat((float) (dimension * Math.cos(angle)));
+ mVertexBuffer.putFloat(0.0f);
+ // Texture coordinates.
+ mTextureBuffer.putFloat((float) (0.5 + 0.5 * Math.sin(angle)));
+ mTextureBuffer.putFloat((float) (0.5 - 0.5 * Math.cos(angle)));
+ }
+
+ if (tessellation == TESSELLATION_TO_CENTER) {
+ // Add center point.
+ mVertexBuffer.putFloat(0.0f);
+ mVertexBuffer.putFloat(0.0f);
+ mVertexBuffer.putFloat(0.0f);
+ mTextureBuffer.putFloat(0.5f);
+ mTextureBuffer.putFloat(0.5f);
+ }
+
+ mIndexBuffer =
+ ByteBuffer.allocateDirect(
+ triangleCount * 3 /* indices per triangle */ * SHORT_SIZE);
+ mIndexBuffer.order(ByteOrder.nativeOrder());
+
+ switch (tessellation) {
+ case TESSELLATION_BASE:
+ for (int i = 0; i < triangleCount; ++i) {
+ mIndexBuffer.putShort((short) 0);
+ mIndexBuffer.putShort((short) (i + 1));
+ mIndexBuffer.putShort((short) (i + 2));
+ }
+ break;
+ case TESSELLATION_TO_CENTER:
+ for (int i = 0; i < triangleCount; ++i) {
+ mIndexBuffer.putShort((short)i);
+ mIndexBuffer.putShort((short)((i + 1) % externalPointCount));
+ mIndexBuffer.putShort((short)externalPointCount);
+ }
+ break;
+ }
+
+ if (mVertexBuffer.remaining() != 0 || mTextureBuffer.remaining() != 0 || mIndexBuffer.remaining() != 0) {
+ throw new RuntimeException("Failed to fill buffers");
+ }
+
+ mVertexBuffer.position(0);
+ mTextureBuffer.position(0);
+ mIndexBuffer.position(0);
+ }
+
+ public float getDimension() {
+ return mDimension;
+ }
+
+ public ByteBuffer getVertexBuffer() {
+ return mVertexBuffer;
+ }
+
+ public ByteBuffer getTextureBuffer() {
+ return mTextureBuffer;
+ }
+
+ public ByteBuffer getIndexBuffer() {
+ return mIndexBuffer;
+ }
+} \ No newline at end of file
diff --git a/tests/GamePerformance/src/android/gameperformance/RenderPatchAnimation.java b/tests/GamePerformance/src/android/gameperformance/RenderPatchAnimation.java
new file mode 100644
index 000000000000..7dcdb00e1014
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/RenderPatchAnimation.java
@@ -0,0 +1,101 @@
+/*
+ * 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.gameperformance;
+
+import java.util.Random;
+
+import android.annotation.NonNull;
+import android.opengl.Matrix;
+
+/**
+ * Class that performs bouncing animation for RenderPatch on the screen.
+ */
+public class RenderPatchAnimation {
+ private final static Random RANDOM = new Random();
+
+ private final RenderPatch mRenderPatch;
+ // Bounds of animation
+ private final float mAvailableX;
+ private final float mAvailableY;
+
+ // Crurrent position.
+ private float mPosX;
+ private float mPosY;
+ // Direction of movement.
+ private float mDirX;
+ private float mDirY;
+
+ private float[] mMatrix;
+
+ public RenderPatchAnimation(@NonNull RenderPatch renderPatch, float ratio) {
+ mRenderPatch = renderPatch;
+
+ mAvailableX = ratio - mRenderPatch.getDimension();
+ mAvailableY = 1.0f - mRenderPatch.getDimension();
+
+ mPosX = 2.0f * mAvailableX * RANDOM.nextFloat() - mAvailableX;
+ mPosY = 2.0f * mAvailableY * RANDOM.nextFloat() - mAvailableY;
+ mMatrix = new float[16];
+
+ // Evenly distributed in cycle, normalized.
+ while (true) {
+ mDirX = 2.0f * RANDOM.nextFloat() - 1.0f;
+ mDirY = mRenderPatch.getDimension() < 1.0f ? 2.0f * RANDOM.nextFloat() - 1.0f : 0.0f;
+
+ final float length = (float)Math.sqrt(mDirX * mDirX + mDirY * mDirY);
+ if (length <= 1.0f && length > 0.0f) {
+ mDirX /= length;
+ mDirY /= length;
+ break;
+ }
+ }
+ }
+
+ @NonNull
+ public RenderPatch getRenderPatch() {
+ return mRenderPatch;
+ }
+
+ /**
+ * Performs the next update. t specifies the distance to travel along the direction. This checks
+ * if patch goes out of screen and invert axis direction if needed.
+ */
+ public void update(float t) {
+ mPosX += mDirX * t;
+ mPosY += mDirY * t;
+ if (mPosX < -mAvailableX) {
+ mDirX = Math.abs(mDirX);
+ } else if (mPosX > mAvailableX) {
+ mDirX = -Math.abs(mDirX);
+ }
+ if (mPosY < -mAvailableY) {
+ mDirY = Math.abs(mDirY);
+ } else if (mPosY > mAvailableY) {
+ mDirY = -Math.abs(mDirY);
+ }
+ }
+
+ /**
+ * Returns Model/View/Projection transform for the patch.
+ */
+ public float[] getTransform(@NonNull float[] vpMatrix) {
+ Matrix.setIdentityM(mMatrix, 0);
+ mMatrix[12] = mPosX;
+ mMatrix[13] = mPosY;
+ Matrix.multiplyMM(mMatrix, 0, vpMatrix, 0, mMatrix, 0);
+ return mMatrix;
+ }
+} \ No newline at end of file
diff --git a/tests/GamePerformance/src/android/gameperformance/RenderPatchOpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/RenderPatchOpenGLTest.java
new file mode 100644
index 000000000000..7492cc034234
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/RenderPatchOpenGLTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.gameperformance;
+
+import java.util.List;
+
+import javax.microedition.khronos.opengles.GL;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+
+/**
+ * Base class for all OpenGL based tests that use RenderPatch as a base.
+ */
+public abstract class RenderPatchOpenGLTest extends OpenGLTest {
+ private final float[] COLOR = new float[] { 1.0f, 1.0f, 1.0f, 1.0f };
+
+ private final String VERTEX_SHADER =
+ "uniform mat4 uMVPMatrix;"
+ + "attribute vec4 vPosition;"
+ + "attribute vec2 vTexture;"
+ + "varying vec2 vTex;"
+ + "void main() {"
+ + " vTex = vTexture;"
+ + " gl_Position = uMVPMatrix * vPosition;"
+ + "}";
+
+ private final String FRAGMENT_SHADER =
+ "precision mediump float;"
+ + "uniform sampler2D uTexture;"
+ + "uniform vec4 uColor;"
+ + "varying vec2 vTex;"
+ + "void main() {"
+ + " vec4 color = texture2D(uTexture, vTex);"
+ + " gl_FragColor = uColor * color;"
+ + "}";
+
+ private List<RenderPatchAnimation> mRenderPatches;
+
+ private int mProgram = -1;
+ private int mMVPMatrixHandle;
+ private int mTextureHandle;
+ private int mPositionHandle;
+ private int mColorHandle;
+ private int mTextureCoordHandle;
+
+ private final float[] mVPMatrix = new float[16];
+
+ public RenderPatchOpenGLTest(@NonNull GamePerformanceActivity activity) {
+ super(activity);
+ }
+
+ protected void setRenderPatches(@NonNull List<RenderPatchAnimation> renderPatches) {
+ mRenderPatches = renderPatches;
+ }
+
+ private void ensureInited() {
+ if (mProgram >= 0) {
+ return;
+ }
+
+ mProgram = OpenGLUtils.createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
+
+ // get handle to fragment shader's uColor member
+ GLES20.glUseProgram(mProgram);
+ OpenGLUtils.checkGlError("useProgram");
+
+ // get handle to shape's transformation matrix
+ mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
+ OpenGLUtils.checkGlError("get uMVPMatrix");
+
+ mTextureHandle = GLES20.glGetUniformLocation(mProgram, "uTexture");
+ OpenGLUtils.checkGlError("uTexture");
+ // get handle to vertex shader's vPosition member
+ mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
+ OpenGLUtils.checkGlError("vPosition");
+ mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, "vTexture");
+ OpenGLUtils.checkGlError("vTexture");
+ mColorHandle = GLES20.glGetUniformLocation(mProgram, "uColor");
+ OpenGLUtils.checkGlError("uColor");
+
+ mTextureHandle = OpenGLUtils.createTexture(getContext(), R.drawable.logo);
+
+ final float[] projectionMatrix = new float[16];
+ final float[] viewMatrix = new float[16];
+
+ final float ratio = getView().getRenderRatio();
+ Matrix.orthoM(projectionMatrix, 0, -ratio, ratio, -1, 1, -1, 1);
+ Matrix.setLookAtM(viewMatrix, 0, 0, 0, -0.5f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
+ Matrix.multiplyMM(mVPMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
+ }
+
+ /**
+ * Returns global color for patch.
+ */
+ public float[] getColor() {
+ return COLOR;
+ }
+
+ /**
+ * Extra setup for particular tests.
+ */
+ public void onBeforeDraw(GL gl) {
+ }
+
+ @Override
+ public void draw(GL gl) {
+ ensureInited();
+
+ GLES20.glUseProgram(mProgram);
+ OpenGLUtils.checkGlError("useProgram");
+
+ GLES20.glDisable(GLES20.GL_BLEND);
+ OpenGLUtils.checkGlError("disableBlend");
+
+ GLES20.glEnableVertexAttribArray(mPositionHandle);
+ OpenGLUtils.checkGlError("enableVertexAttributes");
+
+ GLES20.glEnableVertexAttribArray(mTextureCoordHandle);
+ OpenGLUtils.checkGlError("enableTexturesAttributes");
+
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureHandle);
+ OpenGLUtils.checkGlError("setTexture");
+
+ GLES20.glUniform4fv(mColorHandle, 1, getColor(), 0);
+ OpenGLUtils.checkGlError("setColor");
+
+ onBeforeDraw(gl);
+
+ for (final RenderPatchAnimation renderPatchAnimation : mRenderPatches) {
+
+ renderPatchAnimation.update(0.01f);
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandle,
+ 1,
+ false,
+ renderPatchAnimation.getTransform(mVPMatrix),
+ 0);
+ OpenGLUtils.checkGlError("setTransform");
+
+ GLES20.glVertexAttribPointer(
+ mPositionHandle,
+ RenderPatch.VERTEX_COORD_COUNT,
+ GLES20.GL_FLOAT,
+ false /* normalized */,
+ RenderPatch.VERTEX_STRIDE,
+ renderPatchAnimation.getRenderPatch().getVertexBuffer());
+ OpenGLUtils.checkGlError("setVertexAttribute");
+
+ GLES20.glVertexAttribPointer(
+ mTextureCoordHandle,
+ RenderPatch.TEXTURE_COORD_COUNT,
+ GLES20.GL_FLOAT,
+ false /* normalized */,
+ RenderPatch.TEXTURE_STRIDE,
+ renderPatchAnimation.getRenderPatch().getTextureBuffer());
+ OpenGLUtils.checkGlError("setTextureAttribute");
+
+ // Draw the patch.
+ final int indicesCount =
+ renderPatchAnimation.getRenderPatch().getIndexBuffer().capacity() /
+ RenderPatch.SHORT_SIZE;
+ GLES20.glDrawElements(
+ GLES20.GL_TRIANGLES,
+ indicesCount,
+ GLES20.GL_UNSIGNED_SHORT,
+ renderPatchAnimation.getRenderPatch().getIndexBuffer());
+ OpenGLUtils.checkGlError("drawPatch");
+ }
+
+ GLES20.glDisableVertexAttribArray(mPositionHandle);
+ GLES20.glDisableVertexAttribArray(mTextureCoordHandle);
+ }
+} \ No newline at end of file
diff --git a/tests/GamePerformance/src/android/gameperformance/TriangleCountOpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/TriangleCountOpenGLTest.java
new file mode 100644
index 000000000000..593f37bf9128
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/TriangleCountOpenGLTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.gameperformance;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.annotation.NonNull;
+
+/**
+ * Test that measures maximum amount of triangles can be rasterized keeping FPS close to the device
+ * refresh rate. It is has very few devices call and each call contains big amount of triangles.
+ * Total filling area is around one screen.
+ */
+public class TriangleCountOpenGLTest extends RenderPatchOpenGLTest {
+ // Based on index buffer of short values.
+ private final static int MAX_TRIANGLES_IN_PATCH = 32000;
+
+ public TriangleCountOpenGLTest(@NonNull GamePerformanceActivity activity) {
+ super(activity);
+ }
+
+ @Override
+ public String getName() {
+ return "triangle_count";
+ }
+
+ @Override
+ public String getUnitName() {
+ return "ktriangles";
+ }
+
+ @Override
+ public double getUnitScale() {
+ return 2.0;
+ }
+
+ @Override
+ public void initUnits(double trianlgeCountD) {
+ final int triangleCount = (int)Math.round(trianlgeCountD * 1000.0);
+ final List<RenderPatchAnimation> renderPatches = new ArrayList<>();
+ final int patchCount =
+ (triangleCount + MAX_TRIANGLES_IN_PATCH - 1) / MAX_TRIANGLES_IN_PATCH;
+ final int patchTriangleCount = triangleCount / patchCount;
+ for (int i = 0; i < patchCount; ++i) {
+ final RenderPatch renderPatch = new RenderPatch(patchTriangleCount,
+ 0.5f /* dimension */,
+ RenderPatch.TESSELLATION_TO_CENTER);
+ renderPatches.add(new RenderPatchAnimation(renderPatch, getView().getRenderRatio()));
+ }
+ setRenderPatches(renderPatches);
+ }
+} \ No newline at end of file
diff --git a/tests/JobSchedulerPerfTests/Android.bp b/tests/JobSchedulerPerfTests/Android.bp
new file mode 100644
index 000000000000..c51b811f0735
--- /dev/null
+++ b/tests/JobSchedulerPerfTests/Android.bp
@@ -0,0 +1,26 @@
+// 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.
+
+android_test {
+ name: "JobSchedulerPerfTests",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.test.rules",
+ "apct-perftests-utils",
+ "services",
+ "jobscheduler-service",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/tests/RollbackTest/TestApp/res_v2/values-anydpi/values.xml b/tests/JobSchedulerPerfTests/AndroidManifest.xml
index 9a1aa7fd8461..39e751ca2a0c 100644
--- a/tests/RollbackTest/TestApp/res_v2/values-anydpi/values.xml
+++ b/tests/JobSchedulerPerfTests/AndroidManifest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
+<!-- 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.
@@ -13,7 +13,15 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.perftests.job">
+ <uses-sdk
+ android:minSdkVersion="21" />
-<resources>
- <integer name="split_version">2</integer>
-</resources>
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.perftests.job"/>
+</manifest>
diff --git a/tests/JobSchedulerPerfTests/AndroidTest.xml b/tests/JobSchedulerPerfTests/AndroidTest.xml
new file mode 100644
index 000000000000..ca4b6c83f788
--- /dev/null
+++ b/tests/JobSchedulerPerfTests/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs JobScheduler Performance Tests">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="JobSchedulerPerfTests.apk"/>
+ <option name="cleanup-apks" value="true"/>
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-tag" value="JobSchedulerPerfTests"/>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.frameworks.perftests.job"/>
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration>
diff --git a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
new file mode 100644
index 000000000000..e956be339bc4
--- /dev/null
+++ b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
@@ -0,0 +1,156 @@
+/*
+ * 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 com.android.frameworks.perftests.job;
+
+
+import android.app.job.JobInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.SystemClock;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.PerfManualStatusReporter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.job.JobStore;
+import com.android.server.job.JobStore.JobSet;
+import com.android.server.job.controllers.JobStatus;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class JobStorePerfTests {
+ private static final String SOURCE_PACKAGE = "com.android.frameworks.perftests.job";
+ private static final int SOURCE_USER_ID = 0;
+ private static final int CALLING_UID = 10079;
+
+ private static Context sContext;
+ private static File sTestDir;
+ private static JobStore sJobStore;
+
+ private static List<JobStatus> sFewJobs = new ArrayList<>();
+ private static List<JobStatus> sManyJobs = new ArrayList<>();
+
+ @Rule
+ public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
+
+ @BeforeClass
+ public static void setUpOnce() {
+ sContext = InstrumentationRegistry.getTargetContext();
+ sTestDir = new File(sContext.getFilesDir(), "JobStorePerfTests");
+ sJobStore = JobStore.initAndGetForTesting(sContext, sTestDir);
+
+ for (int i = 0; i < 50; i++) {
+ sFewJobs.add(createJobStatus("fewJobs", i));
+ }
+ for (int i = 0; i < 500; i++) {
+ sManyJobs.add(createJobStatus("manyJobs", i));
+ }
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ sTestDir.deleteOnExit();
+ }
+
+ private void runPersistedJobWriting(List<JobStatus> jobList) {
+ final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
+
+ long elapsedTimeNs = 0;
+ while (benchmarkState.keepRunning(elapsedTimeNs)) {
+ sJobStore.clear();
+ for (JobStatus job : jobList) {
+ sJobStore.add(job);
+ }
+ sJobStore.waitForWriteToCompleteForTesting(10_000);
+
+ final long startTime = SystemClock.elapsedRealtimeNanos();
+ sJobStore.writeStatusToDiskForTesting();
+ final long endTime = SystemClock.elapsedRealtimeNanos();
+ elapsedTimeNs = endTime - startTime;
+ }
+ }
+
+ @Test
+ public void testPersistedJobWriting_fewJobs() {
+ runPersistedJobWriting(sFewJobs);
+ }
+
+ @Test
+ public void testPersistedJobWriting_manyJobs() {
+ runPersistedJobWriting(sManyJobs);
+ }
+
+ private void runPersistedJobReading(List<JobStatus> jobList, boolean rtcIsGood) {
+ final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
+
+ long elapsedTimeNs = 0;
+ while (benchmarkState.keepRunning(elapsedTimeNs)) {
+ sJobStore.clear();
+ for (JobStatus job : jobList) {
+ sJobStore.add(job);
+ }
+ sJobStore.waitForWriteToCompleteForTesting(10_000);
+
+ JobSet jobSet = new JobSet();
+
+ final long startTime = SystemClock.elapsedRealtimeNanos();
+ sJobStore.readJobMapFromDisk(jobSet, rtcIsGood);
+ final long endTime = SystemClock.elapsedRealtimeNanos();
+ elapsedTimeNs = endTime - startTime;
+ }
+ }
+
+ @Test
+ public void testPersistedJobReading_fewJobs_goodRTC() {
+ runPersistedJobReading(sFewJobs, true);
+ }
+
+ @Test
+ public void testPersistedJobReading_fewJobs_badRTC() {
+ runPersistedJobReading(sFewJobs, false);
+ }
+
+ @Test
+ public void testPersistedJobReading_manyJobs_goodRTC() {
+ runPersistedJobReading(sManyJobs, true);
+ }
+
+ @Test
+ public void testPersistedJobReading_manyJobs_badRTC() {
+ runPersistedJobReading(sManyJobs, false);
+ }
+
+ private static JobStatus createJobStatus(String testTag, int jobId) {
+ JobInfo jobInfo = new JobInfo.Builder(jobId,
+ new ComponentName(sContext, "JobStorePerfTestJobService"))
+ .setPersisted(true)
+ .build();
+ return JobStatus.createFromJobInfo(
+ jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+ }
+}
diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp
index 88d92c4d94fe..0b75039cf69f 100644
--- a/tests/PackageWatchdog/Android.bp
+++ b/tests/PackageWatchdog/Android.bp
@@ -23,6 +23,7 @@ android_test {
"androidx.test.rules",
"services.core",
"services.net",
+ "truth-prebuilt",
],
libs: ["android.test.runner"],
jni_libs: [
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index c42201fa0d3e..06b58fda5b7d 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -18,10 +18,8 @@ package com.android.server;
import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -44,6 +42,7 @@ import android.util.AtomicFile;
import androidx.test.InstrumentationRegistry;
+import com.android.server.PackageWatchdog.HealthCheckState;
import com.android.server.PackageWatchdog.MonitoredPackage;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -65,9 +64,6 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
-// TODO: Write test without using PackageWatchdog#getPackages. Just rely on
-// behavior of observers receiving crash notifications or not to determine if it's registered
-// TODO: Use Truth in tests.
/**
* Test PackageWatchdog.
*/
@@ -83,6 +79,7 @@ public class PackageWatchdogTest {
private static final String OBSERVER_NAME_4 = "observer4";
private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5);
+ private final TestClock mTestClock = new TestClock();
private TestLooper mTestLooper;
private Context mSpyContext;
@Mock
@@ -114,78 +111,105 @@ public class PackageWatchdogTest {
dropShellPermissions();
}
- /**
- * Test registration, unregistration, package expiry and duration reduction
- */
@Test
- public void testRegistration() throws Exception {
+ public void testRegistration_singleObserver() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+ // The failed packages should be the same as the registered ones to ensure registration is
+ // done successfully
+ assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
+ }
+
+ @Test
+ public void testRegistration_multiObservers() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
- // Start observing for observer1 which will be unregistered
watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- // Start observing for observer2 which will expire
watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
- // Start observing for observer3 which will have expiry duration reduced
- watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION);
-
- // Verify packages observed at start
- // 1
- assertEquals(1, watchdog.getPackages(observer1).size());
- assertTrue(watchdog.getPackages(observer1).contains(APP_A));
- // 2
- assertEquals(2, watchdog.getPackages(observer2).size());
- assertTrue(watchdog.getPackages(observer2).contains(APP_A));
- assertTrue(watchdog.getPackages(observer2).contains(APP_B));
- // 3
- assertEquals(1, watchdog.getPackages(observer3).size());
- assertTrue(watchdog.getPackages(observer3).contains(APP_A));
-
- // Then unregister observer1
- watchdog.unregisterHealthObserver(observer1);
-
- // Verify observer2 and observer3 left
- // 1
- assertNull(watchdog.getPackages(observer1));
- // 2
- assertEquals(2, watchdog.getPackages(observer2).size());
- assertTrue(watchdog.getPackages(observer2).contains(APP_A));
- assertTrue(watchdog.getPackages(observer2).contains(APP_B));
- // 3
- assertEquals(1, watchdog.getPackages(observer3).size());
- assertTrue(watchdog.getPackages(observer3).contains(APP_A));
-
- // Then advance time a little and run messages in Handlers so observer2 expires
- Thread.sleep(SHORT_DURATION);
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+ new VersionedPackage(APP_B, VERSION_CODE)));
+
+ // The failed packages should be the same as the registered ones to ensure registration is
+ // done successfully
+ assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
+ assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
+ }
- // Verify observer3 left with reduced expiry duration
- // 1
- assertNull(watchdog.getPackages(observer1));
- // 2
- assertNull(watchdog.getPackages(observer2));
- // 3
- assertEquals(1, watchdog.getPackages(observer3).size());
- assertTrue(watchdog.getPackages(observer3).contains(APP_A));
-
- // Then advance time some more and run messages in Handlers so observer3 expires
- Thread.sleep(LONG_DURATION);
- mTestLooper.dispatchAll();
+ @Test
+ public void testUnregistration_singleObserver() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- // Verify observer3 expired
- // 1
- assertNull(watchdog.getPackages(observer1));
- // 2
- assertNull(watchdog.getPackages(observer2));
- // 3
- assertNull(watchdog.getPackages(observer3));
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.unregisterHealthObserver(observer);
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+ // We should have no failed packages to ensure unregistration is done successfully
+ assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+ }
+
+ @Test
+ public void testUnregistration_multiObservers() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.unregisterHealthObserver(observer2);
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+ // observer1 should receive failed packages as intended.
+ assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
+ // observer2 should have no failed packages to ensure unregistration is done successfully
+ assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
+ }
+
+ @Test
+ public void testExpiration_singleObserver() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ moveTimeForwardAndDispatch(SHORT_DURATION);
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+ // We should have no failed packages for the fatal failure is raised after expiration
+ assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+ }
+
+ @Test
+ public void testExpiration_multiObservers() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION);
+ moveTimeForwardAndDispatch(SHORT_DURATION);
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+ // We should have no failed packages for the fatal failure is raised after expiration
+ assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
+ // We should have failed packages since observer2 hasn't expired
+ assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A);
}
/** Observing already observed package extends the observation time. */
@Test
- public void testObserveAlreadyObservedPackage() throws Exception {
+ public void testObserveAlreadyObservedPackage() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
@@ -193,66 +217,47 @@ public class PackageWatchdogTest {
watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then advance time half-way
- Thread.sleep(SHORT_DURATION / 2);
- mTestLooper.dispatchAll();
+ moveTimeForwardAndDispatch(SHORT_DURATION / 2);
// Start observing APP_A again
watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then advance time such that it should have expired were it not for the second observation
- Thread.sleep((SHORT_DURATION / 2) + 1);
- mTestLooper.dispatchAll();
+ moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1);
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- // Verify that APP_A not expired since second observation extended the time
- assertEquals(1, watchdog.getPackages(observer).size());
- assertTrue(watchdog.getPackages(observer).contains(APP_A));
+ // Verify that we receive failed packages as expected for APP_A not expired
+ assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
}
/**
* Test package observers are persisted and loaded on startup
*/
@Test
- public void testPersistence() throws Exception {
+ public void testPersistence() {
PackageWatchdog watchdog1 = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
-
- // Verify 2 observers are registered and saved internally
- // 1
- assertEquals(1, watchdog1.getPackages(observer1).size());
- assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
- // 2
- assertEquals(2, watchdog1.getPackages(observer2).size());
- assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
- assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
-
// Then advance time and run IO Handler so file is saved
mTestLooper.dispatchAll();
-
// Then start a new watchdog
PackageWatchdog watchdog2 = createWatchdog();
-
- // Verify the new watchdog loads observers on startup but nothing registered
- assertEquals(0, watchdog2.getPackages(observer1).size());
- assertEquals(0, watchdog2.getPackages(observer2).size());
- // Verify random observer not saved returns null
- assertNull(watchdog2.getPackages(new TestObserver(OBSERVER_NAME_3)));
-
- // Then register observer1
+ // Then resume observer1 and observer2
watchdog2.registerHealthObserver(observer1);
watchdog2.registerHealthObserver(observer2);
-
- // Verify 2 observers are registered after reload
- // 1
- assertEquals(1, watchdog1.getPackages(observer1).size());
- assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
- // 2
- assertEquals(2, watchdog1.getPackages(observer2).size());
- assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
- assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
+ raiseFatalFailureAndDispatch(watchdog2,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+ new VersionedPackage(APP_B, VERSION_CODE)));
+
+ // We should receive failed packages as expected to ensure observers are persisted and
+ // resumed correctly
+ assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
+ assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
}
/**
@@ -276,8 +281,8 @@ public class PackageWatchdogTest {
mTestLooper.dispatchAll();
// Verify that observers are not notified
- assertEquals(0, observer1.mFailedPackages.size());
- assertEquals(0, observer2.mFailedPackages.size());
+ assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
+ assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
}
/**
@@ -295,16 +300,12 @@ public class PackageWatchdogTest {
watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
// Then fail APP_C (not observed) above the threshold
- for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)));
- }
-
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)));
// Verify that observers are not notified
- assertEquals(0, observer1.mFailedPackages.size());
- assertEquals(0, observer2.mFailedPackages.size());
+ assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
+ assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
}
/**
@@ -329,16 +330,11 @@ public class PackageWatchdogTest {
watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A (different version) above the threshold
- for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(
- new VersionedPackage(APP_A, differentVersionCode)));
- }
-
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, differentVersionCode)));
// Verify that observers are not notified
- assertEquals(0, observer.mFailedPackages.size());
+ assertThat(observer.mHealthCheckFailedPackages).isEmpty();
}
@@ -368,33 +364,26 @@ public class PackageWatchdogTest {
SHORT_DURATION);
// Then fail all apps above the threshold
- for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
- new VersionedPackage(APP_B, VERSION_CODE),
- new VersionedPackage(APP_C, VERSION_CODE),
- new VersionedPackage(APP_D, VERSION_CODE)));
- }
-
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+ new VersionedPackage(APP_B, VERSION_CODE),
+ new VersionedPackage(APP_C, VERSION_CODE),
+ new VersionedPackage(APP_D, VERSION_CODE)));
// Verify least impact observers are notifed of package failures
- List<String> observerNonePackages = observerNone.mFailedPackages;
- List<String> observerHighPackages = observerHigh.mFailedPackages;
- List<String> observerMidPackages = observerMid.mFailedPackages;
- List<String> observerLowPackages = observerLow.mFailedPackages;
+ List<String> observerNonePackages = observerNone.mMitigatedPackages;
+ List<String> observerHighPackages = observerHigh.mMitigatedPackages;
+ List<String> observerMidPackages = observerMid.mMitigatedPackages;
+ List<String> observerLowPackages = observerLow.mMitigatedPackages;
// APP_D failure observed by only observerNone is not caught cos its impact is none
- assertEquals(0, observerNonePackages.size());
+ assertThat(observerNonePackages).isEmpty();
// APP_C failure is caught by observerHigh cos it's the lowest impact observer
- assertEquals(1, observerHighPackages.size());
- assertEquals(APP_C, observerHighPackages.get(0));
+ assertThat(observerHighPackages).containsExactly(APP_C);
// APP_B failure is caught by observerMid cos it's the lowest impact observer
- assertEquals(1, observerMidPackages.size());
- assertEquals(APP_B, observerMidPackages.get(0));
+ assertThat(observerMidPackages).containsExactly(APP_B);
// APP_A failure is caught by observerLow cos it's the lowest impact observer
- assertEquals(1, observerLowPackages.size());
- assertEquals(APP_A, observerLowPackages.get(0));
+ assertThat(observerLowPackages).containsExactly(APP_A);
}
/**
@@ -421,66 +410,51 @@ public class PackageWatchdogTest {
watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
// Then fail APP_A above the threshold
- for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- }
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// Verify only observerFirst is notifed
- assertEquals(1, observerFirst.mFailedPackages.size());
- assertEquals(APP_A, observerFirst.mFailedPackages.get(0));
- assertEquals(0, observerSecond.mFailedPackages.size());
+ assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
+ assertThat(observerSecond.mMitigatedPackages).isEmpty();
// After observerFirst handles failure, next action it has is high impact
observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH;
- observerFirst.mFailedPackages.clear();
- observerSecond.mFailedPackages.clear();
+ observerFirst.mMitigatedPackages.clear();
+ observerSecond.mMitigatedPackages.clear();
// Then fail APP_A again above the threshold
- for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- }
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// Verify only observerSecond is notifed cos it has least impact
- assertEquals(1, observerSecond.mFailedPackages.size());
- assertEquals(APP_A, observerSecond.mFailedPackages.get(0));
- assertEquals(0, observerFirst.mFailedPackages.size());
+ assertThat(observerSecond.mMitigatedPackages).containsExactly(APP_A);
+ assertThat(observerFirst.mMitigatedPackages).isEmpty();
// After observerSecond handles failure, it has no further actions
observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
- observerFirst.mFailedPackages.clear();
- observerSecond.mFailedPackages.clear();
+ observerFirst.mMitigatedPackages.clear();
+ observerSecond.mMitigatedPackages.clear();
// Then fail APP_A again above the threshold
- for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- }
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// Verify only observerFirst is notifed cos it has the only action
- assertEquals(1, observerFirst.mFailedPackages.size());
- assertEquals(APP_A, observerFirst.mFailedPackages.get(0));
- assertEquals(0, observerSecond.mFailedPackages.size());
+ assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
+ assertThat(observerSecond.mMitigatedPackages).isEmpty();
// After observerFirst handles failure, it too has no further actions
observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
- observerFirst.mFailedPackages.clear();
- observerSecond.mFailedPackages.clear();
+ observerFirst.mMitigatedPackages.clear();
+ observerSecond.mMitigatedPackages.clear();
// Then fail APP_A again above the threshold
- for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- }
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// Verify no observer is notified cos no actions left
- assertEquals(0, observerFirst.mFailedPackages.size());
- assertEquals(0, observerSecond.mFailedPackages.size());
+ assertThat(observerFirst.mMitigatedPackages).isEmpty();
+ assertThat(observerSecond.mMitigatedPackages).isEmpty();
}
/**
@@ -499,17 +473,12 @@ public class PackageWatchdogTest {
watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A above the threshold
- for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- }
-
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// Verify only one observer is notifed
- assertEquals(1, observer1.mFailedPackages.size());
- assertEquals(APP_A, observer1.mFailedPackages.get(0));
- assertEquals(0, observer2.mFailedPackages.size());
+ assertThat(observer1.mMitigatedPackages).containsExactly(APP_A);
+ assertThat(observer2.mMitigatedPackages).isEmpty();
}
/**
@@ -538,9 +507,7 @@ public class PackageWatchdogTest {
// Verify we requested health checks for APP_A and APP_B
List<String> requestedPackages = controller.getRequestedPackages();
- assertEquals(2, requestedPackages.size());
- assertEquals(APP_A, requestedPackages.get(0));
- assertEquals(APP_B, requestedPackages.get(1));
+ assertThat(requestedPackages).containsExactly(APP_A, APP_B);
// Then health check passed for APP_A (observer1 is aware)
controller.setPackagePassed(APP_A);
@@ -552,23 +519,19 @@ public class PackageWatchdogTest {
watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION);
// Then expire observers
- Thread.sleep(SHORT_DURATION);
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ moveTimeForwardAndDispatch(SHORT_DURATION);
// Verify we cancelled all requests on expiry
- assertEquals(0, controller.getRequestedPackages().size());
+ assertThat(controller.getRequestedPackages()).isEmpty();
// Verify observer1 is not notified
- assertEquals(0, observer1.mFailedPackages.size());
+ assertThat(observer1.mMitigatedPackages).isEmpty();
// Verify observer2 is notifed because health checks for APP_B never passed
- assertEquals(1, observer2.mFailedPackages.size());
- assertEquals(APP_B, observer2.mFailedPackages.get(0));
+ assertThat(observer2.mMitigatedPackages).containsExactly(APP_B);
// Verify observer3 is notifed because health checks for APP_A did not pass before expiry
- assertEquals(1, observer3.mFailedPackages.size());
- assertEquals(APP_A, observer3.mFailedPackages.get(0));
+ assertThat(observer3.mMitigatedPackages).containsExactly(APP_A);
}
/**
@@ -595,9 +558,7 @@ public class PackageWatchdogTest {
// Verify we requested health checks for APP_A and APP_B
List<String> requestedPackages = controller.getRequestedPackages();
- assertEquals(2, requestedPackages.size());
- assertEquals(APP_A, requestedPackages.get(0));
- assertEquals(APP_B, requestedPackages.get(1));
+ assertThat(requestedPackages).containsExactly(APP_A, APP_B);
// Disable explicit health checks (marks APP_A and APP_B as passed)
setExplicitHealthCheckEnabled(false);
@@ -606,14 +567,13 @@ public class PackageWatchdogTest {
mTestLooper.dispatchAll();
// Verify all checks are cancelled
- assertEquals(0, controller.getRequestedPackages().size());
+ assertThat(controller.getRequestedPackages()).isEmpty();
// Then expire APP_A
- Thread.sleep(SHORT_DURATION);
- mTestLooper.dispatchAll();
+ moveTimeForwardAndDispatch(SHORT_DURATION);
// Verify APP_A is not failed (APP_B) is not expired yet
- assertEquals(0, observer.mFailedPackages.size());
+ assertThat(observer.mMitigatedPackages).isEmpty();
// Re-enable explicit health checks
setExplicitHealthCheckEnabled(true);
@@ -622,7 +582,7 @@ public class PackageWatchdogTest {
mTestLooper.dispatchAll();
// Verify no requests are made cos APP_A is expired and APP_B was marked as passed
- assertEquals(0, controller.getRequestedPackages().size());
+ assertThat(controller.getRequestedPackages()).isEmpty();
// Then set new supported packages
controller.setSupportedPackages(Arrays.asList(APP_C));
@@ -634,16 +594,13 @@ public class PackageWatchdogTest {
// Verify requests are only made for APP_C
requestedPackages = controller.getRequestedPackages();
- assertEquals(1, requestedPackages.size());
- assertEquals(APP_C, requestedPackages.get(0));
+ assertThat(requestedPackages).containsExactly(APP_C);
// Then expire APP_A and APP_C
- Thread.sleep(SHORT_DURATION);
- mTestLooper.dispatchAll();
+ moveTimeForwardAndDispatch(SHORT_DURATION);
// Verify only APP_C is failed because explicit health checks was not supported for APP_A
- assertEquals(1, observer.mFailedPackages.size());
- assertEquals(APP_C, observer.mFailedPackages.get(0));
+ assertThat(observer.mMitigatedPackages).containsExactly(APP_C);
}
/**
@@ -664,21 +621,48 @@ public class PackageWatchdogTest {
watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION);
// Then APP_A has exceeded health check duration
- Thread.sleep(SHORT_DURATION);
- mTestLooper.dispatchAll();
+ moveTimeForwardAndDispatch(SHORT_DURATION);
// Verify that health check is failed
- assertEquals(1, observer.mFailedPackages.size());
- assertEquals(APP_A, observer.mFailedPackages.get(0));
+ assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
- // Then clear failed packages and start observing a random package so requests are synced
- // and PackageWatchdog#onSupportedPackages is called and APP_A has a chance to fail again
- // this time due to package expiry.
- observer.mFailedPackages.clear();
- watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
+ // Clear failed packages and forward time to expire the observation duration
+ observer.mMitigatedPackages.clear();
+ moveTimeForwardAndDispatch(LONG_DURATION);
+
+ // Verify that health check failure is not notified again
+ assertThat(observer.mMitigatedPackages).isEmpty();
+ }
+
+ /**
+ * Tests failure when health check duration is different from package observation duration
+ * Failure is also notified only once.
+ */
+ @Test
+ public void testExplicitHealthCheckFailureAfterExpiry() {
+ TestController controller = new TestController();
+ PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1,
+ PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+
+ // Start observing with explicit health checks for APP_A and
+ // package observation duration == SHORT_DURATION / 2
+ // health check duration == SHORT_DURATION (set by default in the TestController)
+ controller.setSupportedPackages(Arrays.asList(APP_A));
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
+
+ // Forward time to expire the observation duration
+ moveTimeForwardAndDispatch(SHORT_DURATION / 2);
+
+ // Verify that health check is failed
+ assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
+
+ // Clear failed packages and forward time to expire the health check duration
+ observer.mMitigatedPackages.clear();
+ moveTimeForwardAndDispatch(SHORT_DURATION);
// Verify that health check failure is not notified again
- assertTrue(observer.mFailedPackages.isEmpty());
+ assertThat(observer.mMitigatedPackages).isEmpty();
}
/** Tests {@link MonitoredPackage} health check state transitions. */
@@ -694,36 +678,38 @@ public class PackageWatchdogTest {
// Verify transition: inactive -> active -> passed
// Verify initially inactive
- assertEquals(MonitoredPackage.STATE_INACTIVE, m1.getHealthCheckStateLocked());
+ assertThat(m1.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
// Verify still inactive, until we #setHealthCheckActiveLocked
- assertEquals(MonitoredPackage.STATE_INACTIVE, m1.handleElapsedTimeLocked(SHORT_DURATION));
+ assertThat(m1.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.INACTIVE);
// Verify now active
- assertEquals(MonitoredPackage.STATE_ACTIVE, m1.setHealthCheckActiveLocked(SHORT_DURATION));
+ assertThat(m1.setHealthCheckActiveLocked(SHORT_DURATION)).isEqualTo(
+ HealthCheckState.ACTIVE);
// Verify now passed
- assertEquals(MonitoredPackage.STATE_PASSED, m1.tryPassHealthCheckLocked());
+ assertThat(m1.tryPassHealthCheckLocked()).isEqualTo(HealthCheckState.PASSED);
// Verify transition: inactive -> active -> failed
// Verify initially inactive
- assertEquals(MonitoredPackage.STATE_INACTIVE, m2.getHealthCheckStateLocked());
+ assertThat(m2.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
// Verify now active
- assertEquals(MonitoredPackage.STATE_ACTIVE, m2.setHealthCheckActiveLocked(SHORT_DURATION));
+ assertThat(m2.setHealthCheckActiveLocked(SHORT_DURATION)).isEqualTo(
+ HealthCheckState.ACTIVE);
// Verify now failed
- assertEquals(MonitoredPackage.STATE_FAILED, m2.handleElapsedTimeLocked(SHORT_DURATION));
+ assertThat(m2.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.FAILED);
// Verify transition: inactive -> failed
// Verify initially inactive
- assertEquals(MonitoredPackage.STATE_INACTIVE, m3.getHealthCheckStateLocked());
+ assertThat(m3.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
// Verify now failed because package expired
- assertEquals(MonitoredPackage.STATE_FAILED, m3.handleElapsedTimeLocked(LONG_DURATION));
+ assertThat(m3.handleElapsedTimeLocked(LONG_DURATION)).isEqualTo(HealthCheckState.FAILED);
// Verify remains failed even when asked to pass
- assertEquals(MonitoredPackage.STATE_FAILED, m3.tryPassHealthCheckLocked());
+ assertThat(m3.tryPassHealthCheckLocked()).isEqualTo(HealthCheckState.FAILED);
// Verify transition: passed
- assertEquals(MonitoredPackage.STATE_PASSED, m4.getHealthCheckStateLocked());
+ assertThat(m4.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.PASSED);
// Verify remains passed even if health check fails
- assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(SHORT_DURATION));
+ assertThat(m4.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.PASSED);
// Verify remains passed even if package expires
- assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(LONG_DURATION));
+ assertThat(m4.handleElapsedTimeLocked(LONG_DURATION)).isEqualTo(HealthCheckState.PASSED);
}
@Test
@@ -742,8 +728,142 @@ public class PackageWatchdogTest {
mTestLooper.dispatchAll();
// Verify the NetworkStack observer is notified
- assertEquals(1, observer.mFailedPackages.size());
- assertEquals(APP_A, observer.mFailedPackages.get(0));
+ assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
+ }
+
+ /** Test default values are used when device property is invalid. */
+ @Test
+ public void testInvalidConfig_watchdogTriggerFailureCount() {
+ adoptShellPermissions(
+ Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.READ_DEVICE_CONFIG);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+ Integer.toString(-1), /*makeDefault*/false);
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ // Fail APP_A below the threshold which should not trigger package failures
+ for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ }
+ mTestLooper.dispatchAll();
+ assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+
+ // One more to trigger the package failure
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ mTestLooper.dispatchAll();
+ assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
+ }
+
+ /** Test default values are used when device property is invalid. */
+ @Test
+ public void testInvalidConfig_watchdogTriggerDurationMillis() {
+ adoptShellPermissions(
+ Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.READ_DEVICE_CONFIG);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+ Integer.toString(2), /*makeDefault*/false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
+ Integer.toString(-1), /*makeDefault*/false);
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ mTestLooper.dispatchAll();
+ moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ mTestLooper.dispatchAll();
+
+ // We shouldn't receive APP_A since the interval of 2 failures is greater than
+ // DEFAULT_TRIGGER_FAILURE_DURATION_MS.
+ assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)));
+ mTestLooper.dispatchAll();
+ moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1);
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)));
+ mTestLooper.dispatchAll();
+
+ // We should receive APP_B since the interval of 2 failures is less than
+ // DEFAULT_TRIGGER_FAILURE_DURATION_MS.
+ assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_B);
+ }
+
+ /**
+ * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
+ * an invalid durationMs.
+ */
+ @Test
+ public void testInvalidMonitoringDuration_beforeExpiry() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+ // Note: Don't move too close to the expiration time otherwise the handler will be thrashed
+ // by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very
+ // small timeouts.
+ moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS - 100);
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+ // We should receive APP_A since the observer hasn't expired
+ assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
+ }
+
+ /**
+ * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
+ * an invalid durationMs.
+ */
+ @Test
+ public void testInvalidMonitoringDuration_afterExpiry() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+ moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1);
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+ // We should receive nothing since the observer has expired
+ assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+ }
+
+ /** Test we are notified when enough failures are triggered within any window. */
+ @Test
+ public void testFailureTriggerWindow() {
+ adoptShellPermissions(
+ Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.READ_DEVICE_CONFIG);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+ Integer.toString(3), /*makeDefault*/false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
+ Integer.toString(1000), /*makeDefault*/false);
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
+ // Raise 2 failures at t=0 and t=900 respectively
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ mTestLooper.dispatchAll();
+ moveTimeForwardAndDispatch(900);
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ mTestLooper.dispatchAll();
+
+ // Raise 2 failures at t=1100
+ moveTimeForwardAndDispatch(200);
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ mTestLooper.dispatchAll();
+
+ // We should receive APP_A since there are 3 failures within 1000ms window
+ assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
}
private void adoptShellPermissions(String... permissions) {
@@ -772,6 +892,21 @@ public class PackageWatchdogTest {
}
}
+ private void moveTimeForwardAndDispatch(long milliSeconds) {
+ mTestClock.moveTimeForward(milliSeconds);
+ mTestLooper.moveTimeForward(milliSeconds);
+ mTestLooper.dispatchAll();
+ }
+
+ /** Trigger package failures above the threshold. */
+ private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
+ List<VersionedPackage> packages) {
+ for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
+ watchdog.onPackageFailure(packages);
+ }
+ mTestLooper.dispatchAll();
+ }
+
private PackageWatchdog createWatchdog() {
return createWatchdog(new TestController(), true /* withPackagesReady */);
}
@@ -782,15 +917,15 @@ public class PackageWatchdogTest {
Handler handler = new Handler(mTestLooper.getLooper());
PackageWatchdog watchdog =
new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller,
- mConnectivityModuleConnector);
+ mConnectivityModuleConnector, mTestClock);
// Verify controller is not automatically started
- assertFalse(controller.mIsEnabled);
+ assertThat(controller.mIsEnabled).isFalse();
if (withPackagesReady) {
// Only capture the NetworkStack callback for the latest registered watchdog
reset(mConnectivityModuleConnector);
watchdog.onPackagesReady();
// Verify controller by default is started when packages are ready
- assertTrue(controller.mIsEnabled);
+ assertThat(controller.mIsEnabled).isTrue();
verify(mConnectivityModuleConnector).registerHealthListener(
mConnectivityModuleCallbackCaptor.capture());
@@ -801,7 +936,8 @@ public class PackageWatchdogTest {
private static class TestObserver implements PackageHealthObserver {
private final String mName;
private int mImpact;
- final List<String> mFailedPackages = new ArrayList<>();
+ final List<String> mHealthCheckFailedPackages = new ArrayList<>();
+ final List<String> mMitigatedPackages = new ArrayList<>();
TestObserver(String name) {
mName = name;
@@ -814,11 +950,12 @@ public class PackageWatchdogTest {
}
public int onHealthCheckFailed(VersionedPackage versionedPackage) {
+ mHealthCheckFailedPackages.add(versionedPackage.getPackageName());
return mImpact;
}
public boolean execute(VersionedPackage versionedPackage) {
- mFailedPackages.add(versionedPackage.getPackageName());
+ mMitigatedPackages.add(versionedPackage.getPackageName());
return true;
}
@@ -888,4 +1025,17 @@ public class PackageWatchdogTest {
}
}
}
+
+ private static class TestClock implements PackageWatchdog.SystemClock {
+ // Note 0 is special to the internal clock of PackageWatchdog. We need to start from
+ // a non-zero value in order not to disrupt the logic of PackageWatchdog.
+ private long mUpTimeMillis = 1;
+ @Override
+ public long uptimeMillis() {
+ return mUpTimeMillis;
+ }
+ public void moveTimeForward(long milliSeconds) {
+ mUpTimeMillis += milliSeconds;
+ }
+ }
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java
index c21c4033a0ff..3415d2e13974 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java
@@ -384,55 +384,55 @@ public class ProtoInputStreamBoolTest extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readInt(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readLong(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java
index 09fe40edda6c..8796807c0521 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java
@@ -306,55 +306,55 @@ public class ProtoInputStreamBytesTest extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readInt(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readLong(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java
index 118fe3431e01..2b54e960eabb 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java
@@ -611,55 +611,55 @@ public class ProtoInputStreamDoubleTest extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readInt(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readLong(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java
index f55d95129588..19bad7099622 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java
@@ -454,55 +454,55 @@ public class ProtoInputStreamEnumTest extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readLong(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java
index df68476f0c36..2bc61a0c7e8a 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java
@@ -431,55 +431,55 @@ public class ProtoInputStreamFixed32Test extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readLong(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java
index af4130b28cd8..a54ecf99d62c 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java
@@ -532,55 +532,55 @@ public class ProtoInputStreamFixed64Test extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readInt(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java
index 9bc07dc513e1..0477e9ea74e0 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java
@@ -563,55 +563,55 @@ public class ProtoInputStreamFloatTest extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readInt(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readLong(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java
index 0065870486f2..a7f3f65e504d 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java
@@ -449,55 +449,55 @@ public class ProtoInputStreamInt32Test extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readLong(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java
index 4d6d105e60b0..dc42468e0605 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java
@@ -529,55 +529,55 @@ public class ProtoInputStreamInt64Test extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readInt(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java
index 5e49eeafb8af..1c0832e3e676 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java
@@ -391,55 +391,55 @@ public class ProtoInputStreamObjectTest extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readInt(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readLong(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java
index 75c88a44614b..d349ea2baa67 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java
@@ -431,55 +431,55 @@ public class ProtoInputStreamSFixed32Test extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readLong(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java
index 4c65cf49318d..81a9c591b32e 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java
@@ -531,55 +531,55 @@ public class ProtoInputStreamSFixed64Test extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readInt(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java
index 6854cd8aad28..97194444deb2 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java
@@ -431,55 +431,55 @@ public class ProtoInputStreamSInt32Test extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readLong(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java
index c53e9d72562a..118476cdf235 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java
@@ -506,55 +506,55 @@ public class ProtoInputStreamSInt64Test extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readInt(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java
index 816d5f900a3d..51ee78f32767 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java
@@ -287,55 +287,55 @@ public class ProtoInputStreamStringTest extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readInt(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readLong(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java
index 50fc537767a4..42f3e991f6e8 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java
@@ -448,55 +448,55 @@ public class ProtoInputStreamUInt32Test extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readLong(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java
index 20969e9056a9..8ba2c0ccaca9 100644
--- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java
@@ -525,55 +525,55 @@ public class ProtoInputStreamUInt64Test extends TestCase {
};
ProtoInputStream pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readFloat(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readDouble(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readInt(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBoolean(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readBytes(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
pi = new ProtoInputStream(protobuf);
- pi.isNextField(fieldId1);
+ pi.nextField();
try {
pi.readString(fieldId1);
- fail("Should have throw IllegalArgumentException");
+ fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException iae) {
// good
}
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index aec40558ad51..231d045bd817 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -12,88 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-android_test_helper_app {
- name: "RollbackTestAppAv1",
- manifest: "TestApp/Av1.xml",
- sdk_version: "current",
- srcs: ["TestApp/src/**/*.java"],
- resource_dirs: ["TestApp/res_v1"],
-}
-
-android_test_helper_app {
- name: "RollbackTestAppAv2",
- manifest: "TestApp/Av2.xml",
- sdk_version: "current",
- srcs: ["TestApp/src/**/*.java"],
- resource_dirs: ["TestApp/res_v2"],
-}
-
-android_test_helper_app {
- name: "RollbackTestAppAv3",
- manifest: "TestApp/Av3.xml",
- sdk_version: "current",
- srcs: ["TestApp/src/**/*.java"],
- resource_dirs: ["TestApp/res_v3"],
-}
-
-android_test_helper_app {
- name: "RollbackTestAppACrashingV2",
- manifest: "TestApp/ACrashingV2.xml",
- sdk_version: "current",
- srcs: ["TestApp/src/**/*.java"],
- resource_dirs: ["TestApp/res_v2"],
-}
-
-android_test_helper_app {
- name: "RollbackTestAppBv1",
- manifest: "TestApp/Bv1.xml",
- sdk_version: "current",
- srcs: ["TestApp/src/**/*.java"],
- resource_dirs: ["TestApp/res_v1"],
-}
-
-android_test_helper_app {
- name: "RollbackTestAppBv2",
- manifest: "TestApp/Bv2.xml",
- sdk_version: "current",
- srcs: ["TestApp/src/**/*.java"],
- resource_dirs: ["TestApp/res_v2"],
-}
-
-android_test_helper_app {
- name: "RollbackTestAppASplitV1",
- manifest: "TestApp/Av1.xml",
- sdk_version: "current",
- srcs: ["TestApp/src/**/*.java"],
- resource_dirs: ["TestApp/res_v1"],
- package_splits: ["anydpi"],
-}
-
-android_test_helper_app {
- name: "RollbackTestAppASplitV2",
- manifest: "TestApp/Av2.xml",
- sdk_version: "current",
- srcs: ["TestApp/src/**/*.java"],
- resource_dirs: ["TestApp/res_v2"],
- package_splits: ["anydpi"],
-}
-
android_test {
name: "RollbackTest",
manifest: "RollbackTest/AndroidManifest.xml",
srcs: ["RollbackTest/src/**/*.java"],
- static_libs: ["androidx.test.rules"],
+ static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
test_suites: ["general-tests"],
- java_resources: [
- ":RollbackTestAppAv1",
- ":RollbackTestAppAv2",
- ":RollbackTestAppAv3",
- ":RollbackTestAppACrashingV2",
- ":RollbackTestAppBv1",
- ":RollbackTestAppBv2",
- ":RollbackTestAppASplitV1",
- ":RollbackTestAppASplitV2",
- ],
test_config: "RollbackTest.xml",
// TODO: sdk_version: "test_current" when Intent#resolveSystemservice is TestApi
}
@@ -105,3 +29,11 @@ java_test_host {
test_suites: ["general-tests"],
test_config: "StagedRollbackTest.xml",
}
+
+java_test_host {
+ name: "MultiUserRollbackTest",
+ srcs: ["MultiUserRollbackTest/src/**/*.java"],
+ libs: ["tradefed"],
+ test_suites: ["general-tests"],
+ test_config: "MultiUserRollbackTest.xml",
+}
diff --git a/tests/RollbackTest/TestApp/res_v1/values/values.xml b/tests/RollbackTest/MultiUserRollbackTest.xml
index 0447c74a79a6..41cec461c377 100644
--- a/tests/RollbackTest/TestApp/res_v1/values/values.xml
+++ b/tests/RollbackTest/MultiUserRollbackTest.xml
@@ -13,8 +13,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<resources>
- <integer name="app_version">1</integer>
- <integer name="split_version">0</integer>
-</resources>
+<configuration description="Runs rollback tests for multiple users">
+ <option name="test-suite-tag" value="MultiUserRollbackTest" />
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class" value="com.android.tests.rollback.host.MultiUserRollbackTest" />
+ </test>
+</configuration>
diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
new file mode 100644
index 000000000000..52f6eba4072b
--- /dev/null
+++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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 com.android.tests.rollback.host;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs rollback tests for multiple users.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class MultiUserRollbackTest extends BaseHostJUnit4Test {
+ // The user that was running originally when the test starts.
+ private int mOriginalUserId;
+ private int mSecondaryUserId = -1;
+ private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60;
+ private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000;
+
+
+ @After
+ public void tearDown() throws Exception {
+ getDevice().switchUser(mOriginalUserId);
+ getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A");
+ removeSecondaryUserIfNecessary();
+ }
+
+ @Before
+ public void setup() throws Exception {
+ mOriginalUserId = getDevice().getCurrentUser();
+ installPackageAsUser("RollbackTest.apk", true, mOriginalUserId);
+ createAndSwitchToSecondaryUserIfNecessary();
+ installPackageAsUser("RollbackTest.apk", true, mSecondaryUserId);
+ }
+
+ @Test
+ public void testBasicForSecondaryUser() throws Exception {
+ runPhaseForUsers("testBasic", mSecondaryUserId);
+ }
+
+ @Test
+ public void testMultipleUsers() throws Exception {
+ runPhaseForUsers("testMultipleUsersInstallV1", mOriginalUserId, mSecondaryUserId);
+ runPhaseForUsers("testMultipleUsersUpgradeToV2", mOriginalUserId);
+ runPhaseForUsers("testMultipleUsersUpdateUserData", mOriginalUserId, mSecondaryUserId);
+ switchToUser(mOriginalUserId);
+ getDevice().executeShellCommand("pm rollback-app com.android.cts.install.lib.testapp.A");
+ runPhaseForUsers("testMultipleUsersVerifyUserdataRollback", mOriginalUserId,
+ mSecondaryUserId);
+ }
+
+ /**
+ * Run the phase for the given user ids, in the order they are given.
+ */
+ private void runPhaseForUsers(String phase, int... userIds) throws Exception {
+ for (int userId: userIds) {
+ switchToUser(userId);
+ assertTrue(runDeviceTests("com.android.tests.rollback",
+ "com.android.tests.rollback.MultiUserRollbackTest",
+ phase));
+ }
+ }
+
+ private void removeSecondaryUserIfNecessary() throws Exception {
+ if (mSecondaryUserId != -1) {
+ getDevice().removeUser(mSecondaryUserId);
+ mSecondaryUserId = -1;
+ }
+ }
+
+ private void createAndSwitchToSecondaryUserIfNecessary() throws Exception {
+ if (mSecondaryUserId == -1) {
+ mOriginalUserId = getDevice().getCurrentUser();
+ mSecondaryUserId = getDevice().createUser("MultiUserRollbackTest_User"
+ + System.currentTimeMillis());
+ switchToUser(mSecondaryUserId);
+ }
+ }
+
+ private void switchToUser(int userId) throws Exception {
+ if (getDevice().getCurrentUser() == userId) {
+ return;
+ }
+
+ assertTrue(getDevice().switchUser(userId));
+ for (int i = 0; i < SWITCH_USER_COMPLETED_NUMBER_OF_POLLS; ++i) {
+ String userState = getDevice().executeShellCommand("am get-started-user-state "
+ + userId);
+ if (userState.contains("RUNNING_UNLOCKED")) {
+ return;
+ }
+ Thread.sleep(SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS);
+ }
+ fail("User switch to user " + userId + " timed out");
+ }
+}
diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml
index 70cd86783d6d..a14b01c57b1b 100644
--- a/tests/RollbackTest/RollbackTest.xml
+++ b/tests/RollbackTest/RollbackTest.xml
@@ -22,8 +22,9 @@
<option name="package" value="com.android.tests.rollback" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
- <!-- Exclude the StagedRollbackTest tests, which needs to be specially
- driven from the StagedRollbackTest host test -->
+ <!-- Exclude the StagedRollbackTest and MultiUserRollbackTest tests, which need to be
+ specially driven from the StagedRollbackTest and MultiUserRollbackTest host test -->
<option name="exclude-filter" value="com.android.tests.rollback.StagedRollbackTest" />
+ <option name="exclude-filter" value="com.android.tests.rollback.MultiUserRollbackTest" />
</test>
</configuration>
diff --git a/tests/RollbackTest/RollbackTest/AndroidManifest.xml b/tests/RollbackTest/RollbackTest/AndroidManifest.xml
index 5380dc9fc8cd..2b8c96484210 100644
--- a/tests/RollbackTest/RollbackTest/AndroidManifest.xml
+++ b/tests/RollbackTest/RollbackTest/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.android.tests.rollback" >
<application>
- <receiver android:name="com.android.tests.rollback.LocalIntentSender"
+ <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
android:exported="true" />
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/LocalIntentSender.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/LocalIntentSender.java
deleted file mode 100644
index 267ef7377b36..000000000000
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/LocalIntentSender.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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 com.android.tests.rollback;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-
-import androidx.test.InstrumentationRegistry;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-/**
- * Make IntentSender that sends intent locally.
- */
-public class LocalIntentSender extends BroadcastReceiver {
-
- private static final String TAG = "RollbackTest";
-
- private static final BlockingQueue<Intent> sIntentSenderResults = new LinkedBlockingQueue<>();
-
- @Override
- public void onReceive(Context context, Intent intent) {
- sIntentSenderResults.add(intent);
- }
-
- /**
- * Get a LocalIntentSender.
- */
- static IntentSender getIntentSender() {
- Context context = InstrumentationRegistry.getContext();
- Intent intent = new Intent(context, LocalIntentSender.class);
- PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, 0);
- return pending.getIntentSender();
- }
-
- /**
- * Returns the most recent Intent sent by a LocalIntentSender.
- */
- static Intent getIntentSenderResult() throws InterruptedException {
- return sIntentSenderResults.take();
- }
-}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
new file mode 100644
index 000000000000..0ffe041b0377
--- /dev/null
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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 com.android.tests.rollback;
+
+import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
+import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.rollback.lib.Rollback;
+import com.android.cts.rollback.lib.RollbackUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+
+@RunWith(JUnit4.class)
+public class MultiUserRollbackTest {
+
+ @Before
+ public void adoptShellPermissions() {
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.TEST_MANAGE_ROLLBACKS,
+ Manifest.permission.MANAGE_ROLLBACKS);
+ }
+
+ @After
+ public void dropShellPermissions() {
+ InstallUtils.dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testBasic() throws Exception {
+ new RollbackTest().testBasic();
+ }
+
+ /**
+ * Install version 1 of the test app. This method is run for both users.
+ */
+ @Test
+ public void testMultipleUsersInstallV1() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+ }
+
+ /**
+ * Upgrade the test app to version 2. This method should only run once as the system user,
+ * and will update the app for both users.
+ */
+ @Test
+ public void testMultipleUsersUpgradeToV2() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TestApp.A);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ }
+
+ /**
+ * This method is run for both users. Assert that the test app has upgraded for both users, and
+ * update their userdata to reflect this new version.
+ */
+ @Test
+ public void testMultipleUsersUpdateUserData() {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ InstallUtils.processUserData(TestApp.A);
+ }
+
+ /**
+ * The system will have rolled back the test app at this stage. Verify that the rollback has
+ * taken place, and that the userdata has been correctly rolled back. This method is run for
+ * both users.
+ */
+ @Test
+ public void testMultipleUsersVerifyUserdataRollback() {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+ }
+}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
deleted file mode 100644
index ebe54187ddb6..000000000000
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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 com.android.tests.rollback;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A broadcast receiver that can be used to get
- * ACTION_ROLLBACK_COMMITTED broadcasts.
- */
-class RollbackBroadcastReceiver extends BroadcastReceiver {
-
- private static final String TAG = "RollbackTest";
-
- private final BlockingQueue<Intent> mRollbackBroadcasts = new LinkedBlockingQueue<>();
-
- /**
- * Creates a RollbackBroadcastReceiver and registers it with the given
- * context.
- */
- RollbackBroadcastReceiver() {
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_ROLLBACK_COMMITTED);
- InstrumentationRegistry.getContext().registerReceiver(this, filter);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.i(TAG, "Received rollback broadcast intent");
- mRollbackBroadcasts.add(intent);
- }
-
- /**
- * Polls for at most the given amount of time for the next rollback
- * broadcast.
- */
- Intent poll(long timeout, TimeUnit unit) throws InterruptedException {
- return mRollbackBroadcasts.poll(timeout, unit);
- }
-
- /**
- * Waits forever for the next rollback broadcast.
- */
- Intent take() throws InterruptedException {
- return mRollbackBroadcasts.take();
- }
-
- /**
- * Unregisters this broadcast receiver.
- */
- void unregister() {
- InstrumentationRegistry.getContext().unregisterReceiver(this);
- }
-}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 1b002cadb07f..ed8f272874e1 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -16,14 +16,14 @@
package com.android.tests.rollback;
-import static com.android.tests.rollback.RollbackTestUtils.assertPackageRollbackInfoEquals;
-import static com.android.tests.rollback.RollbackTestUtils.assertRollbackInfoEquals;
-import static com.android.tests.rollback.RollbackTestUtils.getUniqueRollbackInfoForPackage;
-import static com.android.tests.rollback.RollbackTestUtils.processUserData;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
+import static com.android.cts.install.lib.InstallUtils.processUserData;
+import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
+import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage;
+import static com.android.cts.rollback.lib.RollbackUtils.waitForAvailableRollback;
+import static com.android.cts.rollback.lib.RollbackUtils.waitForUnavailableRollback;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.fail;
import android.Manifest;
@@ -31,15 +31,21 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.VersionedPackage;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.provider.DeviceConfig;
-import android.provider.Settings;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+import com.android.cts.rollback.lib.Rollback;
+import com.android.cts.rollback.lib.RollbackBroadcastReceiver;
+import com.android.cts.rollback.lib.RollbackUtils;
+
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -57,8 +63,6 @@ public class RollbackTest {
private static final String TAG = "RollbackTest";
- private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A";
- private static final String TEST_APP_B = "com.android.tests.rollback.testapp.B";
private static final String INSTRUMENTED_APP = "com.android.tests.rollback";
// copied from PackageManagerService#PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS
@@ -88,7 +92,7 @@ public class RollbackTest {
context.registerReceiver(enableRollbackReceiver, enableRollbackFilter);
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
@@ -97,18 +101,18 @@ public class RollbackTest {
// Register a broadcast receiver for notification when the
// rollback has been committed.
RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
- // Uninstall TEST_APP_A
- RollbackTestUtils.uninstall(TEST_APP_A);
- assertEquals(-1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ // Uninstall TestApp.A
+ Uninstall.packages(TestApp.A);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
// TODO: There is currently a race condition between when the app is
// uninstalled and when rollback manager deletes the rollback. Fix it
// so that's not the case!
for (int i = 0; i < 5; ++i) {
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+ rm.getRecentlyCommittedRollbacks(), TestApp.A);
if (rollback != null) {
Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
Thread.sleep(1000);
@@ -116,50 +120,55 @@ public class RollbackTest {
}
// The app should not be available for rollback.
- assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
+ waitForUnavailableRollback(TestApp.A);
// There should be no recently committed rollbacks for this package.
- assertNull(getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TEST_APP_A));
+ assertThat(getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TestApp.A)).isNull();
// Install v1 of the app (without rollbacks enabled).
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
// Upgrade from v1 to v2, with rollbacks enabled.
- RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
// The app should now be available for rollback.
- RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
+ RollbackInfo available = waitForAvailableRollback(TestApp.A);
+ assertThat(available).isNotStaged();
+ assertThat(available).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
// We should not have received any rollback requests yet.
// TODO: Possibly flaky if, by chance, some other app on device
// happens to be rolled back at the same time?
- assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS));
+ assertThat(broadcastReceiver.poll(0, TimeUnit.SECONDS)).isNull();
// Roll back the app.
- RollbackTestUtils.rollback(rollback.getRollbackId());
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ RollbackUtils.rollback(available.getRollbackId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
// Verify we received a broadcast for the rollback.
// TODO: Race condition between the timeout and when the broadcast is
// received could lead to test flakiness.
Intent broadcast = broadcastReceiver.poll(5, TimeUnit.SECONDS);
- assertNotNull(broadcast);
- assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS));
+ assertThat(broadcast).isNotNull();
+ assertThat(broadcastReceiver.poll(0, TimeUnit.SECONDS)).isNull();
// Verify the recent rollback has been recorded.
- rollback = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
+ RollbackInfo committed = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TestApp.A);
+ assertThat(committed).isNotNull();
+ assertThat(committed).isNotStaged();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(committed).hasRollbackId(available.getRollbackId());
broadcastReceiver.unregister();
context.unregisterReceiver(enableRollbackReceiver);
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -169,50 +178,54 @@ public class RollbackTest {
@Test
public void testAvailableRollbackPersistence() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
- RollbackTestUtils.uninstall(TEST_APP_B);
- RollbackTestUtils.install("RollbackTestAppBv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppBv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ Uninstall.packages(TestApp.B);
+ Install.single(TestApp.B1).commit();
+ Install.single(TestApp.B2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
// Both test apps should now be available for rollback.
- RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
+ RollbackInfo rollbackA = waitForAvailableRollback(TestApp.A);
+ assertThat(rollbackA).isNotNull();
+ assertThat(rollbackA).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
- RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_B);
- assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
+ RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B);
+ assertThat(rollbackB).isNotNull();
+ assertThat(rollbackB).packagesContainsExactly(
+ Rollback.from(TestApp.B2).to(TestApp.B1));
// Reload the persisted data.
rm.reloadPersistedData();
// The apps should still be available for rollback.
- rollbackA = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
+ rollbackA = waitForAvailableRollback(TestApp.A);
+ assertThat(rollbackA).isNotNull();
+ assertThat(rollbackA).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
- rollbackB = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_B);
- assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
+ rollbackB = waitForAvailableRollback(TestApp.B);
+ assertThat(rollbackB).isNotNull();
+ assertThat(rollbackB).packagesContainsExactly(
+ Rollback.from(TestApp.B2).to(TestApp.B1));
// Rollback of B should not rollback A
- RollbackTestUtils.rollback(rollbackB.getRollbackId());
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ RollbackUtils.rollback(rollbackB.getRollbackId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -222,49 +235,76 @@ public class RollbackTest {
@Test
public void testAvailableMultiPackageRollbackPersistence() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.uninstall(TEST_APP_B);
- RollbackTestUtils.installMultiPackage(false,
- "RollbackTestAppAv1.apk",
- "RollbackTestAppBv1.apk");
- RollbackTestUtils.installMultiPackage(true,
- "RollbackTestAppAv2.apk",
- "RollbackTestAppBv2.apk");
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ Uninstall.packages(TestApp.A, TestApp.B);
+ Install.multi(TestApp.A1, TestApp.B1).commit();
+ Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
+
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
// The app should now be available for rollback.
- RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoForAandB(rollbackA);
+ RollbackInfo availableA = waitForAvailableRollback(TestApp.A);
+ assertThat(availableA).isNotNull();
+ assertThat(availableA).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1),
+ Rollback.from(TestApp.B2).to(TestApp.B1));
- RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_B);
- assertRollbackInfoForAandB(rollbackB);
+ RollbackInfo availableB = waitForAvailableRollback(TestApp.B);
+ assertThat(availableB).isNotNull();
+ assertThat(availableB).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1),
+ Rollback.from(TestApp.B2).to(TestApp.B1));
+
+ // Assert they're both the same rollback
+ assertThat(availableA).hasRollbackId(availableB.getRollbackId());
// Reload the persisted data.
rm.reloadPersistedData();
// The apps should still be available for rollback.
- rollbackA = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoForAandB(rollbackA);
-
- rollbackB = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B);
- assertRollbackInfoForAandB(rollbackB);
+ availableA = waitForAvailableRollback(TestApp.A);
+ assertThat(availableA).isNotNull();
+ assertThat(availableA).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1),
+ Rollback.from(TestApp.B2).to(TestApp.B1));
+
+ availableB = waitForAvailableRollback(TestApp.B);
+ assertThat(availableB).isNotNull();
+ assertThat(availableB).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1),
+ Rollback.from(TestApp.B2).to(TestApp.B1));
// Rollback of B should rollback A as well
- RollbackTestUtils.rollback(rollbackB.getRollbackId());
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ RollbackUtils.rollback(availableB.getRollbackId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+
+ RollbackInfo committedA = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TestApp.A);
+ assertThat(committedA).isNotNull();
+ assertThat(committedA).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1),
+ Rollback.from(TestApp.B2).to(TestApp.B1));
+
+ RollbackInfo committedB = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TestApp.A);
+ assertThat(committedB).isNotNull();
+ assertThat(committedB).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1),
+ Rollback.from(TestApp.B2).to(TestApp.B1));
+
+ // Assert they're both the same rollback
+ assertThat(committedA).hasRollbackId(committedB.getRollbackId());
+ assertThat(committedA).hasRollbackId(availableA.getRollbackId());
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -274,42 +314,49 @@ public class RollbackTest {
@Test
public void testRecentlyCommittedRollbackPersistence() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
// The app should now be available for rollback.
- RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
+ RollbackInfo available = waitForAvailableRollback(TestApp.A);
+ assertThat(available).isNotNull();
// Roll back the app.
- VersionedPackage cause = new VersionedPackage(
- "com.android.tests.rollback.testapp.Foo", 42);
- RollbackTestUtils.rollback(rollback.getRollbackId(), cause);
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ TestApp cause = new TestApp("Foo", "com.android.tests.rollback.testapp.Foo",
+ /*versionCode*/ 42, /*isApex*/ false);
+ RollbackUtils.rollback(available.getRollbackId(), cause);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
// Verify the recent rollback has been recorded.
- rollback = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback, cause);
+ RollbackInfo committed = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TestApp.A);
+ assertThat(committed).isNotNull();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(committed).causePackagesContainsExactly(cause);
// Reload the persisted data.
rm.reloadPersistedData();
// Verify the recent rollback is still recorded.
- rollback = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback, cause);
+ committed = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TestApp.A);
+ assertThat(committed).isNotNull();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(committed).causePackagesContainsExactly(cause);
+ assertThat(committed).hasRollbackId(available.getRollbackId());
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -320,10 +367,10 @@ public class RollbackTest {
public void testRollbackExpiresAfterLifetime() throws Exception {
long expirationTime = TimeUnit.SECONDS.toMillis(30);
long defaultExpirationTime = TimeUnit.HOURS.toMillis(48);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
@@ -336,39 +383,43 @@ public class RollbackTest {
// Pull the new expiration time from DeviceConfig
rm.reloadPersistedData();
- // Uninstall TEST_APP_A
- RollbackTestUtils.uninstall(TEST_APP_A);
- assertEquals(-1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ // Uninstall TestApp.A
+ Uninstall.packages(TestApp.A);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
// Install v1 of the app (without rollbacks enabled).
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
// Upgrade from v1 to v2, with rollbacks enabled.
- RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
// Check that the rollback data has not expired
Thread.sleep(1000);
- RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
+ RollbackInfo rollback = waitForAvailableRollback(TestApp.A);
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
- // Give it a little more time, but still not the long enough to expire
+ // Give it a little more time, but still not long enough to expire
Thread.sleep(expirationTime / 2);
rollback = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
+ rm.getAvailableRollbacks(), TestApp.A);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
// Check that the data has expired after the expiration time (with a buffer of 1 second)
Thread.sleep(expirationTime / 2);
- assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
+ rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TestApp.A);
+ assertThat(rollback).isNull();
} finally {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
Long.toString(defaultExpirationTime), false /* makeDefault*/);
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -380,10 +431,10 @@ public class RollbackTest {
public void testTimeChangeDoesNotAffectLifetime() throws Exception {
long expirationTime = TimeUnit.SECONDS.toMillis(30);
long defaultExpirationTime = TimeUnit.HOURS.toMillis(48);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
@@ -398,24 +449,25 @@ public class RollbackTest {
rm.reloadPersistedData();
// Install app A with rollback enabled
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
Thread.sleep(expirationTime / 2);
// Install app B with rollback enabled
- RollbackTestUtils.uninstall(TEST_APP_B);
- RollbackTestUtils.install("RollbackTestAppBv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppBv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ Uninstall.packages(TestApp.B);
+ Install.single(TestApp.B1).commit();
+ Install.single(TestApp.B2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+
// 1 second buffer
Thread.sleep(1000);
try {
// Change the time
- RollbackTestUtils.forwardTimeBy(expirationTime);
+ RollbackUtils.forwardTimeBy(expirationTime);
// 1 second buffer to allow Rollback Manager to handle time change before loading
// persisted data
@@ -427,24 +479,31 @@ public class RollbackTest {
// Wait until rollback for app A has expired
// This will trigger an expiration run that should expire app A but not B
Thread.sleep(expirationTime / 2);
- assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
+ RollbackInfo rollback =
+ getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A);
+ assertThat(rollback).isNull();
// Rollback for app B should not be expired
- RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_B);
- assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollback);
+ rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TestApp.B);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.B2).to(TestApp.B1));
// Wait until rollback for app B has expired
Thread.sleep(expirationTime / 2);
- assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B));
+ rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TestApp.B);
+ // Rollback should be expired by now
+ assertThat(rollback).isNull();
} finally {
- RollbackTestUtils.forwardTimeBy(-expirationTime);
+ RollbackUtils.forwardTimeBy(-expirationTime);
}
} finally {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
Long.toString(defaultExpirationTime), false /* makeDefault*/);
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -455,30 +514,30 @@ public class RollbackTest {
@Test
public void testRollbackExpiration() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
// The app should now be available for rollback.
- RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
+ RollbackInfo rollback = waitForAvailableRollback(TestApp.A);
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
// Expire the rollback.
- rm.expireRollbackForPackage(TEST_APP_A);
+ rm.expireRollbackForPackage(TestApp.A);
// The rollback should no longer be available.
- assertNull(getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A));
+ assertThat(getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TestApp.A)).isNull();
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -488,24 +547,22 @@ public class RollbackTest {
@Test
public void testUserDataRollback() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- processUserData(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
- processUserData(TEST_APP_A);
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ processUserData(TestApp.A);
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ processUserData(TestApp.A);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
- RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- RollbackTestUtils.rollback(rollback.getRollbackId());
- processUserData(TEST_APP_A);
+ RollbackInfo rollback = waitForAvailableRollback(TestApp.A);
+ RollbackUtils.rollback(rollback.getRollbackId());
+ processUserData(TestApp.A);
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -515,30 +572,23 @@ public class RollbackTest {
@Test
public void testRollbackWithSplits() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.installSplit(false,
- "RollbackTestAppASplitV1.apk",
- "RollbackTestAppASplitV1_anydpi.apk");
- processUserData(TEST_APP_A);
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.ASplit1).commit();
+ processUserData(TestApp.A);
- RollbackTestUtils.installSplit(true,
- "RollbackTestAppASplitV2.apk",
- "RollbackTestAppASplitV2_anydpi.apk");
- processUserData(TEST_APP_A);
+ Install.single(TestApp.ASplit2).setEnableRollback().commit();
+ processUserData(TestApp.A);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
- RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertNotNull(rollback);
- RollbackTestUtils.rollback(rollback.getRollbackId());
- processUserData(TEST_APP_A);
+ RollbackInfo rollback = waitForAvailableRollback(TestApp.A);
+ RollbackUtils.rollback(rollback.getRollbackId());
+ processUserData(TestApp.A);
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -559,7 +609,7 @@ public class RollbackTest {
// Confirm that we really haven't received the broadcast.
// TODO: How long to wait for the expected timeout?
- assertNull(broadcastReceiver.poll(5, TimeUnit.SECONDS));
+ assertThat(broadcastReceiver.poll(5, TimeUnit.SECONDS)).isNull();
// TODO: Do we need to do this? Do we need to ensure this is always
// called, even when the test fails?
@@ -573,48 +623,47 @@ public class RollbackTest {
@Test
public void testMultipleRollbackAvailable() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
// Prep installation of the test apps.
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
- RollbackTestUtils.uninstall(TEST_APP_B);
- RollbackTestUtils.install("RollbackTestAppBv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppBv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ Uninstall.packages(TestApp.B);
+ Install.single(TestApp.B1).commit();
+ Install.single(TestApp.B2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
// Both test apps should now be available for rollback, and the
// RollbackInfo returned for the rollbacks should be correct.
- RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
+ RollbackInfo rollbackA = waitForAvailableRollback(TestApp.A);
+ assertThat(rollbackA).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
- RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_B);
- assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
+ RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B);
+ assertThat(rollbackB).packagesContainsExactly(
+ Rollback.from(TestApp.B2).to(TestApp.B1));
// Executing rollback should roll back the correct package.
- RollbackTestUtils.rollback(rollbackA.getRollbackId());
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
-
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
-
- RollbackTestUtils.rollback(rollbackB.getRollbackId());
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ RollbackUtils.rollback(rollbackA.getRollbackId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+ RollbackUtils.rollback(rollbackB.getRollbackId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -626,7 +675,7 @@ public class RollbackTest {
public void testManageRollbacksPermission() throws Exception {
// We shouldn't be allowed to call any of the RollbackManager APIs
// without the MANAGE_ROLLBACKS permission.
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
try {
rm.getAvailableRollbacks();
@@ -659,7 +708,7 @@ public class RollbackTest {
}
try {
- rm.expireRollbackForPackage(TEST_APP_A);
+ rm.expireRollbackForPackage(TestApp.A);
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
@@ -673,26 +722,27 @@ public class RollbackTest {
@Test
public void testEnableRollbackPermission() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES);
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", /* enableRollback */ false);
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
- RollbackTestUtils.install("RollbackTestAppAv2.apk", /* enableRollback */ true);
+ Install.single(TestApp.A2).setEnableRollback().commit();
// We expect v2 of the app was installed, but rollback has not
// been enabled.
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
- assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(
+ getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull();
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -703,25 +753,26 @@ public class RollbackTest {
@Test
public void testNonModuleEnableRollback() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.MANAGE_ROLLBACKS);
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", /* enableRollback */ false);
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
- RollbackTestUtils.install("RollbackTestAppAv2.apk", /* enableRollback */ true);
+ Install.single(TestApp.A2).setEnableRollback().commit();
// We expect v2 of the app was installed, but rollback has not
// been enabled because the test app is not a module.
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
- assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(
+ getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull();
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -731,54 +782,53 @@ public class RollbackTest {
@Test
public void testMultiPackage() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
// Prep installation of the test apps.
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.uninstall(TEST_APP_B);
- RollbackTestUtils.installMultiPackage(false,
- "RollbackTestAppAv1.apk",
- "RollbackTestAppBv1.apk");
- processUserData(TEST_APP_A);
- processUserData(TEST_APP_B);
- RollbackTestUtils.installMultiPackage(true,
- "RollbackTestAppAv2.apk",
- "RollbackTestAppBv2.apk");
- processUserData(TEST_APP_A);
- processUserData(TEST_APP_B);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
-
- // TEST_APP_A should now be available for rollback.
- RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoForAandB(rollback);
+ Uninstall.packages(TestApp.A, TestApp.B);
+ Install.multi(TestApp.A1, TestApp.B1).commit();
+ processUserData(TestApp.A);
+ processUserData(TestApp.B);
+ Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
+ processUserData(TestApp.A);
+ processUserData(TestApp.B);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+
+ // TestApp.A should now be available for rollback.
+ RollbackInfo rollback = waitForAvailableRollback(TestApp.A);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1),
+ Rollback.from(TestApp.B2).to(TestApp.B1));
// Rollback the app. It should cause both test apps to be rolled
// back.
- RollbackTestUtils.rollback(rollback.getRollbackId());
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ RollbackUtils.rollback(rollback.getRollbackId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
// We should see recent rollbacks listed for both A and B.
Thread.sleep(1000);
RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+ rm.getRecentlyCommittedRollbacks(), TestApp.A);
RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TEST_APP_B);
- assertRollbackInfoForAandB(rollbackB);
+ rm.getRecentlyCommittedRollbacks(), TestApp.B);
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1),
+ Rollback.from(TestApp.B2).to(TestApp.B1));
- assertEquals(rollbackA.getRollbackId(), rollbackB.getRollbackId());
+ assertThat(rollbackA).hasRollbackId(rollbackB.getRollbackId());
- processUserData(TEST_APP_A);
- processUserData(TEST_APP_B);
+ processUserData(TestApp.A);
+ processUserData(TestApp.B);
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -790,31 +840,27 @@ public class RollbackTest {
@Test
public void testMultiPackageEnableFail() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
-
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.uninstall(TEST_APP_B);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ Uninstall.packages(TestApp.A, TestApp.B);
+ Install.single(TestApp.A1).commit();
// We should fail to enable rollback here because TestApp B is not
// already installed.
- RollbackTestUtils.installMultiPackage(true,
- "RollbackTestAppAv2.apk",
- "RollbackTestAppBv2.apk");
+ Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
- assertNull(getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A));
- assertNull(getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_B));
+ assertThat(getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TestApp.A)).isNull();
+ assertThat(getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TestApp.B)).isNull();
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -825,30 +871,33 @@ public class RollbackTest {
*/
public void testSameVersionUpdate() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
- RollbackTestUtils.install("RollbackTestAppACrashingV2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ Install.single(TestApp.ACrashing2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 2, rollback);
+ rm.getAvailableRollbacks(), TestApp.A);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A2));
- RollbackTestUtils.rollback(rollback.getRollbackId());
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ RollbackUtils.rollback(rollback.getRollbackId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
rollback = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 2, rollback);
+ rm.getRecentlyCommittedRollbacks(), TestApp.A);
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A2));
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -857,60 +906,54 @@ public class RollbackTest {
*/
@Test
public void testBadUpdateRollback() throws Exception {
- BroadcastReceiver crashCountReceiver = null;
Context context = InstrumentationRegistry.getContext();
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.MANAGE_ROLLBACKS,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
- Manifest.permission.KILL_BACKGROUND_PROCESSES,
+ Manifest.permission.FORCE_STOP_PACKAGES,
Manifest.permission.RESTART_PACKAGES);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
// Prep installation of the test apps.
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppACrashingV2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Uninstall.packages(TestApp.A, TestApp.B);
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.ACrashing2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
- RollbackTestUtils.uninstall(TEST_APP_B);
- RollbackTestUtils.install("RollbackTestAppBv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppBv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ Install.single(TestApp.B1).commit();
+ Install.single(TestApp.B2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
// Both test apps should now be available for rollback, and the
// targetPackage returned for rollback should be correct.
- RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
+ RollbackInfo rollbackA = waitForAvailableRollback(TestApp.A);
+ assertThat(rollbackA).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
- RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_B);
- assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
+ RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B);
+ assertThat(rollbackB).packagesContainsExactly(
+ Rollback.from(TestApp.B2).to(TestApp.B1));
// Register rollback committed receiver
RollbackBroadcastReceiver rollbackReceiver = new RollbackBroadcastReceiver();
- // Crash TEST_APP_A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
- crashCountReceiver = RollbackTestUtils.sendCrashBroadcast(context, TEST_APP_A, 5);
+ // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
+ RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
// Verify we received a broadcast for the rollback.
rollbackReceiver.take();
- // TEST_APP_A is automatically rolled back by the RollbackPackageHealthObserver
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ // TestApp.A is automatically rolled back by the RollbackPackageHealthObserver
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
// Instrumented app is still the package installer
- String installer = context.getPackageManager().getInstallerPackageName(TEST_APP_A);
- assertEquals(INSTRUMENTED_APP, installer);
- // TEST_APP_B is untouched
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ String installer = context.getPackageManager().getInstallerPackageName(TestApp.A);
+ assertThat(installer).isEqualTo(INSTRUMENTED_APP);
+ // TestApp.B is untouched
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
- if (crashCountReceiver != null) {
- context.unregisterReceiver(crashCountReceiver);
- }
+ InstallUtils.dropShellPermissionIdentity();
}
}
@@ -920,31 +963,31 @@ public class RollbackTest {
@Test
public void testRollForwardRace() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
Manifest.permission.MANAGE_ROLLBACKS);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
- RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
+ RollbackInfo rollback = waitForAvailableRollback(TestApp.A);
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
// Install a new version of package A, then immediately rollback
// the previous version. We expect the rollback to fail, because
// it is no longer available.
// There are a couple different ways this could fail depending on
// thread interleaving, so don't ignore flaky failures.
- RollbackTestUtils.install("RollbackTestAppAv3.apk", false);
+ Install.single(TestApp.A3).commit();
try {
- RollbackTestUtils.rollback(rollback.getRollbackId());
+ RollbackUtils.rollback(rollback.getRollbackId());
// Note: Don't ignore flaky failures here.
fail("Expected rollback to fail, but it did not.");
} catch (AssertionError e) {
@@ -953,17 +996,16 @@ public class RollbackTest {
}
// Note: Don't ignore flaky failures here.
- assertEquals(3, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(3);
} finally {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
}
@Test
- @Ignore("b/136605788")
public void testEnableRollbackTimeoutFailsRollback() throws Exception {
try {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
@@ -973,36 +1015,32 @@ public class RollbackTest {
//setting the timeout to a very short amount that will definitely be triggered
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
- Long.toString(1), false /* makeDefault*/);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
-
- RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
- RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
-
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
-
- assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
+ Long.toString(0), false /* makeDefault*/);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ waitForUnavailableRollback(TestApp.A);
+
+ // Block the RollbackManager to make extra sure it will not be
+ // able to enable the rollback in time.
+ rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(1));
+ Install.single(TestApp.A2).setEnableRollback().commit();
+
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+ // Give plenty of time for RollbackManager to unblock and attempt
+ // to make the rollback available before asserting that the
+ // rollback was not made available.
+ Thread.sleep(TimeUnit.SECONDS.toMillis(2));
+ assertThat(
+ getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull();
} finally {
//setting the timeout back to default
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
null, false /* makeDefault*/);
- RollbackTestUtils.dropShellPermissionIdentity();
- }
- }
-
- // Helper function to test that the given rollback info is a rollback for
- // the atomic set {A2, B2} -> {A1, B1}.
- private void assertRollbackInfoForAandB(RollbackInfo rollback) {
- assertNotNull(rollback);
- assertEquals(2, rollback.getPackages().size());
- if (TEST_APP_A.equals(rollback.getPackages().get(0).getPackageName())) {
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(0));
- assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(1));
- } else {
- assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(0));
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(1));
+ InstallUtils.dropShellPermissionIdentity();
}
}
}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
deleted file mode 100644
index a9e20cdb191b..000000000000
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * 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 com.android.tests.rollback;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.content.rollback.PackageRollbackInfo;
-import android.content.rollback.RollbackInfo;
-import android.content.rollback.RollbackManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.SynchronousQueue;
-
-/**
- * Utilities to facilitate testing rollbacks.
- */
-class RollbackTestUtils {
-
- private static final String TAG = "RollbackTest";
-
- static RollbackManager getRollbackManager() {
- Context context = InstrumentationRegistry.getContext();
- RollbackManager rm = (RollbackManager) context.getSystemService(Context.ROLLBACK_SERVICE);
- if (rm == null) {
- throw new AssertionError("Failed to get RollbackManager");
- }
- return rm;
- }
-
- private static void setTime(long millis) {
- Context context = InstrumentationRegistry.getContext();
- AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- am.setTime(millis);
- }
-
- static void forwardTimeBy(long offsetMillis) {
- setTime(System.currentTimeMillis() + offsetMillis);
- Log.i(TAG, "Forwarded time on device by " + offsetMillis + " millis");
- }
-
- /**
- * Returns the version of the given package installed on device.
- * Returns -1 if the package is not currently installed.
- */
- static long getInstalledVersion(String packageName) {
- PackageInfo pi = getPackageInfo(packageName);
- if (pi == null) {
- return -1;
- } else {
- return pi.getLongVersionCode();
- }
- }
-
- private static boolean isSystemAppWithoutUpdate(String packageName) {
- PackageInfo pi = getPackageInfo(packageName);
- if (pi == null) {
- return false;
- } else {
- return ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)
- && ((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0);
- }
- }
-
- private static PackageInfo getPackageInfo(String packageName) {
- Context context = InstrumentationRegistry.getContext();
- PackageManager pm = context.getPackageManager();
- try {
- return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
- private static void assertStatusSuccess(Intent result) {
- int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_FAILURE);
- if (status == -1) {
- throw new AssertionError("PENDING USER ACTION");
- } else if (status > 0) {
- String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
- throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
- }
- }
-
- /**
- * Uninstalls the given package.
- * Does nothing if the package is not installed.
- * @throws AssertionError if package can't be uninstalled.
- */
- static void uninstall(String packageName) throws InterruptedException, IOException {
- // No need to uninstall if the package isn't installed or is installed on /system.
- if (getInstalledVersion(packageName) == -1 || isSystemAppWithoutUpdate(packageName)) {
- return;
- }
-
- Context context = InstrumentationRegistry.getContext();
- PackageManager packageManager = context.getPackageManager();
- PackageInstaller packageInstaller = packageManager.getPackageInstaller();
- packageInstaller.uninstall(packageName, LocalIntentSender.getIntentSender());
- assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
- }
-
- /**
- * Commit the given rollback.
- * @throws AssertionError if the rollback fails.
- */
- static void rollback(int rollbackId, VersionedPackage... causePackages)
- throws InterruptedException {
- RollbackManager rm = getRollbackManager();
- rm.commitRollback(rollbackId, Arrays.asList(causePackages),
- LocalIntentSender.getIntentSender());
- Intent result = LocalIntentSender.getIntentSenderResult();
- int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
- RollbackManager.STATUS_FAILURE);
- if (status != RollbackManager.STATUS_SUCCESS) {
- String message = result.getStringExtra(RollbackManager.EXTRA_STATUS_MESSAGE);
- throw new AssertionError(message);
- }
- }
-
- /**
- * Installs the apk with the given name.
- *
- * @param resourceName name of class loader resource for the apk to
- * install.
- * @param enableRollback if rollback should be enabled.
- * @throws AssertionError if the installation fails.
- */
- static void install(String resourceName, boolean enableRollback)
- throws InterruptedException, IOException {
- installSplit(enableRollback, resourceName);
- }
-
- /**
- * Installs the apk with the given name and its splits.
- *
- * @param enableRollback if rollback should be enabled.
- * @param resourceNames names of class loader resources for the apk and
- * its splits to install.
- * @throws AssertionError if the installation fails.
- */
- static void installSplit(boolean enableRollback, String... resourceNames)
- throws InterruptedException, IOException {
- Context context = InstrumentationRegistry.getContext();
- PackageInstaller.Session session = null;
- PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- params.setEnableRollback(enableRollback);
- int sessionId = packageInstaller.createSession(params);
- session = packageInstaller.openSession(sessionId);
-
- ClassLoader loader = RollbackTest.class.getClassLoader();
- for (String resourceName : resourceNames) {
- try (OutputStream packageInSession = session.openWrite(resourceName, 0, -1);
- InputStream is = loader.getResourceAsStream(resourceName);) {
- byte[] buffer = new byte[4096];
- int n;
- while ((n = is.read(buffer)) >= 0) {
- packageInSession.write(buffer, 0, n);
- }
- }
- }
-
- // Commit the session (this will start the installation workflow).
- session.commit(LocalIntentSender.getIntentSender());
- assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
- }
-
- /** Launches {@code packageName} with {@link Intent#ACTION_MAIN}. */
- private static void launchPackage(String packageName)
- throws InterruptedException, IOException {
- Context context = InstrumentationRegistry.getContext();
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setPackage(packageName);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- context.startActivity(intent);
- }
-
- /**
- * Installs the APKs or APEXs with the given resource names as an atomic
- * set. A resource is assumed to be an APEX if it has the .apex extension.
- * <p>
- * In case of staged installs, this function will return succesfully after
- * the staged install has been committed and is ready for the device to
- * reboot.
- *
- * @param staged if the rollback should be staged.
- * @param enableRollback if rollback should be enabled.
- * @param resourceNames names of the class loader resource for the apks to
- * install.
- * @throws AssertionError if the installation fails.
- */
- private static void install(boolean staged, boolean enableRollback,
- String... resourceNames) throws InterruptedException, IOException {
- Context context = InstrumentationRegistry.getContext();
- PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
-
- PackageInstaller.SessionParams multiPackageParams = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- multiPackageParams.setMultiPackage();
- if (staged) {
- multiPackageParams.setStaged();
- }
- // TODO: Do we set this on the parent params, the child params, or
- // both?
- multiPackageParams.setEnableRollback(enableRollback);
- int multiPackageId = packageInstaller.createSession(multiPackageParams);
- PackageInstaller.Session multiPackage = packageInstaller.openSession(multiPackageId);
-
- for (String resourceName : resourceNames) {
- PackageInstaller.Session session = null;
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- if (staged) {
- params.setStaged();
- }
- if (resourceName.endsWith(".apex")) {
- params.setInstallAsApex();
- }
- params.setEnableRollback(enableRollback);
- int sessionId = packageInstaller.createSession(params);
- session = packageInstaller.openSession(sessionId);
-
- ClassLoader loader = RollbackTest.class.getClassLoader();
- try (OutputStream packageInSession = session.openWrite(resourceName, 0, -1);
- InputStream is = loader.getResourceAsStream(resourceName);) {
- byte[] buffer = new byte[4096];
- int n;
- while ((n = is.read(buffer)) >= 0) {
- packageInSession.write(buffer, 0, n);
- }
- }
- multiPackage.addChildSessionId(sessionId);
- }
-
- // Commit the session (this will start the installation workflow).
- multiPackage.commit(LocalIntentSender.getIntentSender());
- assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
-
- if (staged) {
- waitForSessionReady(multiPackageId);
- }
- }
-
- /**
- * Installs the apks with the given resource names as an atomic set.
- *
- * @param enableRollback if rollback should be enabled.
- * @param resourceNames names of the class loader resource for the apks to
- * install.
- * @throws AssertionError if the installation fails.
- */
- static void installMultiPackage(boolean enableRollback, String... resourceNames)
- throws InterruptedException, IOException {
- install(false, enableRollback, resourceNames);
- }
-
- /**
- * Installs the APKs or APEXs with the given resource names as a staged
- * atomic set. A resource is assumed to be an APEX if it has the .apex
- * extension.
- *
- * @param enableRollback if rollback should be enabled.
- * @param resourceNames names of the class loader resource for the apks to
- * install.
- * @throws AssertionError if the installation fails.
- */
- static void installStaged(boolean enableRollback, String... resourceNames)
- throws InterruptedException, IOException {
- install(true, enableRollback, resourceNames);
- }
-
- static void adoptShellPermissionIdentity(String... permissions) {
- InstrumentationRegistry
- .getInstrumentation()
- .getUiAutomation()
- .adoptShellPermissionIdentity(permissions);
- }
-
- static void dropShellPermissionIdentity() {
- InstrumentationRegistry
- .getInstrumentation()
- .getUiAutomation()
- .dropShellPermissionIdentity();
- }
-
- /**
- * Returns the RollbackInfo with a given package in the list of rollbacks.
- * Throws an assertion failure if there is more than one such rollback
- * info. Returns null if there are no such rollback infos.
- */
- static RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks,
- String packageName) {
- RollbackInfo found = null;
- for (RollbackInfo rollback : rollbacks) {
- for (PackageRollbackInfo info : rollback.getPackages()) {
- if (packageName.equals(info.getPackageName())) {
- assertNull(found);
- found = rollback;
- break;
- }
- }
- }
- return found;
- }
-
- /**
- * Asserts that the given PackageRollbackInfo has the expected package
- * name and versions.
- */
- static void assertPackageRollbackInfoEquals(String packageName,
- long versionRolledBackFrom, long versionRolledBackTo,
- PackageRollbackInfo info) {
- assertEquals(packageName, info.getPackageName());
- assertEquals(packageName, info.getVersionRolledBackFrom().getPackageName());
- assertEquals(versionRolledBackFrom, info.getVersionRolledBackFrom().getLongVersionCode());
- assertEquals(packageName, info.getVersionRolledBackTo().getPackageName());
- assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode());
- }
-
- /**
- * Asserts that the given RollbackInfo has the given packages with expected
- * package names and all are rolled to and from the same given versions.
- */
- static void assertRollbackInfoEquals(String[] packageNames,
- long versionRolledBackFrom, long versionRolledBackTo,
- RollbackInfo info, VersionedPackage... causePackages) {
- assertNotNull(info);
- assertEquals(packageNames.length, info.getPackages().size());
- int foundPackages = 0;
- for (String packageName : packageNames) {
- for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
- if (packageName.equals(pkgRollbackInfo.getPackageName())) {
- foundPackages++;
- assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom,
- versionRolledBackTo, pkgRollbackInfo);
- break;
- }
- }
- }
- assertEquals(packageNames.length, foundPackages);
- assertEquals(causePackages.length, info.getCausePackages().size());
- for (int i = 0; i < causePackages.length; ++i) {
- assertEquals(causePackages[i].getPackageName(),
- info.getCausePackages().get(i).getPackageName());
- assertEquals(causePackages[i].getLongVersionCode(),
- info.getCausePackages().get(i).getLongVersionCode());
- }
- }
-
- /**
- * Asserts that the given RollbackInfo has a single package with expected
- * package name and versions.
- */
- static void assertRollbackInfoEquals(String packageName,
- long versionRolledBackFrom, long versionRolledBackTo,
- RollbackInfo info, VersionedPackage... causePackages) {
- String[] packageNames = {packageName};
- assertRollbackInfoEquals(packageNames, versionRolledBackFrom, versionRolledBackTo, info,
- causePackages);
- }
-
- /**
- * Waits for the given session to be marked as ready.
- * Throws an assertion if the session fails.
- */
- static void waitForSessionReady(int sessionId) {
- BlockingQueue<PackageInstaller.SessionInfo> sessionStatus = new LinkedBlockingQueue<>();
- BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- PackageInstaller.SessionInfo info =
- intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
- if (info != null && info.getSessionId() == sessionId) {
- if (info.isStagedSessionReady() || info.isStagedSessionFailed()) {
- try {
- sessionStatus.put(info);
- } catch (InterruptedException e) {
- Log.e(TAG, "Failed to put session info.", e);
- }
- }
- }
- }
- };
- IntentFilter sessionUpdatedFilter =
- new IntentFilter(PackageInstaller.ACTION_SESSION_UPDATED);
-
- Context context = InstrumentationRegistry.getContext();
- context.registerReceiver(sessionUpdatedReceiver, sessionUpdatedFilter);
-
- PackageInstaller installer = context.getPackageManager().getPackageInstaller();
- PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId);
-
- try {
- if (info.isStagedSessionReady() || info.isStagedSessionFailed()) {
- sessionStatus.put(info);
- }
-
- info = sessionStatus.take();
- context.unregisterReceiver(sessionUpdatedReceiver);
- if (info.isStagedSessionFailed()) {
- throw new AssertionError(info.getStagedSessionErrorMessage());
- }
- } catch (InterruptedException e) {
- throw new AssertionError(e);
- }
- }
-
- private static final String NO_RESPONSE = "NO RESPONSE";
-
- /**
- * Calls into the test app to process user data.
- * Asserts if the user data could not be processed or was version
- * incompatible with the previously processed user data.
- */
- static void processUserData(String packageName) {
- Intent intent = new Intent();
- intent.setComponent(new ComponentName(packageName,
- "com.android.tests.rollback.testapp.ProcessUserData"));
- Context context = InstrumentationRegistry.getContext();
-
- HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
- handlerThread.start();
-
- // It can sometimes take a while after rollback before the app will
- // receive this broadcast, so try a few times in a loop.
- String result = NO_RESPONSE;
- for (int i = 0; result.equals(NO_RESPONSE) && i < 5; ++i) {
- BlockingQueue<String> resultQueue = new LinkedBlockingQueue<>();
- context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (getResultCode() == 1) {
- resultQueue.add("OK");
- } else {
- // If the test app doesn't receive the broadcast or
- // fails to set the result data, then getResultData
- // here returns the initial NO_RESPONSE data passed to
- // the sendOrderedBroadcast call.
- resultQueue.add(getResultData());
- }
- }
- }, new Handler(handlerThread.getLooper()), 0, NO_RESPONSE, null);
-
- try {
- result = resultQueue.take();
- } catch (InterruptedException e) {
- throw new AssertionError(e);
- }
- }
-
- handlerThread.quit();
- if (!"OK".equals(result)) {
- fail(result);
- }
- }
-
- /**
- * Return the rollback info for a recently committed rollback, by matching the rollback id, or
- * return null if no matching rollback is found.
- */
- static RollbackInfo getRecentlyCommittedRollbackInfoById(int getRollbackId) {
- for (RollbackInfo info : getRollbackManager().getRecentlyCommittedRollbacks()) {
- if (info.getRollbackId() == getRollbackId) {
- return info;
- }
- }
- return null;
- }
-
- /**
- * Send broadcast to crash {@code packageName} {@code count} times. If {@code count} is at least
- * {@link PackageWatchdog#TRIGGER_FAILURE_COUNT}, watchdog crash detection will be triggered.
- */
- static BroadcastReceiver sendCrashBroadcast(Context context, String packageName, int count)
- throws InterruptedException, IOException {
- BlockingQueue<Integer> crashQueue = new SynchronousQueue<>();
- IntentFilter crashCountFilter = new IntentFilter();
- crashCountFilter.addAction("com.android.tests.rollback.CRASH");
- crashCountFilter.addCategory(Intent.CATEGORY_DEFAULT);
-
- BroadcastReceiver crashCountReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- try {
- // Sleep long enough for packagewatchdog to be notified of crash
- Thread.sleep(1000);
- // Kill app and close AppErrorDialog
- ActivityManager am = context.getSystemService(ActivityManager.class);
- am.killBackgroundProcesses(packageName);
- // Allow another package launch
- crashQueue.put(intent.getIntExtra("count", 0));
- } catch (InterruptedException e) {
- fail("Failed to communicate with test app");
- }
- }
- };
- context.registerReceiver(crashCountReceiver, crashCountFilter);
-
- do {
- launchPackage(packageName);
- } while(crashQueue.take() < count);
- return crashCountReceiver;
- }
-}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 1a29c4c11457..9e6ac8ef679b 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -16,26 +16,34 @@
package com.android.tests.rollback;
-import static com.android.tests.rollback.RollbackTestUtils.assertRollbackInfoEquals;
-import static com.android.tests.rollback.RollbackTestUtils.getUniqueRollbackInfoForPackage;
+import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
+import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
import android.Manifest;
-import android.content.BroadcastReceiver;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.VersionedPackage;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
+import android.text.TextUtils;
import androidx.test.InstrumentationRegistry;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.LocalIntentSender;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+import com.android.cts.rollback.lib.Rollback;
+import com.android.cts.rollback.lib.RollbackUtils;
+import com.android.internal.R;
import org.junit.After;
import org.junit.Before;
@@ -54,23 +62,21 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class StagedRollbackTest {
- private static final String TAG = "RollbackTest";
- private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A";
- private static final String TEST_APP_A_V1 = "RollbackTestAppAv1.apk";
- private static final String TEST_APP_A_CRASHING_V2 = "RollbackTestAppACrashingV2.apk";
private static final String NETWORK_STACK_CONNECTOR_CLASS =
"android.net.INetworkStackConnector";
+ private static final String MODULE_META_DATA_PACKAGE = getModuleMetadataPackageName();
+
/**
* Adopts common shell permissions needed for rollback tests.
*/
@Before
public void adoptShellPermissions() {
- RollbackTestUtils.adoptShellPermissionIdentity(
+ InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
- Manifest.permission.KILL_BACKGROUND_PROCESSES);
+ Manifest.permission.FORCE_STOP_PACKAGES);
}
/**
@@ -78,7 +84,7 @@ public class StagedRollbackTest {
*/
@After
public void dropShellPermissions() {
- RollbackTestUtils.dropShellPermissionIdentity();
+ InstallUtils.dropShellPermissionIdentity();
}
/**
@@ -87,14 +93,14 @@ public class StagedRollbackTest {
*/
@Test
public void testBadApkOnlyEnableRollback() throws Exception {
- RollbackTestUtils.uninstall(TEST_APP_A);
- assertEquals(-1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ Uninstall.packages(TestApp.A);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
- RollbackTestUtils.install(TEST_APP_A_V1, false);
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
- RollbackTestUtils.processUserData(TEST_APP_A);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
- RollbackTestUtils.installStaged(true, TEST_APP_A_CRASHING_V2);
+ Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit();
// At this point, the host test driver will reboot the device and run
// testBadApkOnlyConfirmEnableRollback().
@@ -106,14 +112,16 @@ public class StagedRollbackTest {
*/
@Test
public void testBadApkOnlyConfirmEnableRollback() throws Exception {
- assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
- RollbackTestUtils.processUserData(TEST_APP_A);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ InstallUtils.processUserData(TestApp.A);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getAvailableRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
- assertTrue(rollback.isStaged());
+ rm.getAvailableRollbacks(), TestApp.A);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(rollback.isStaged()).isTrue();
// At this point, the host test driver will run
// testBadApkOnlyTriggerRollback().
@@ -126,18 +134,8 @@ public class StagedRollbackTest {
*/
@Test
public void testBadApkOnlyTriggerRollback() throws Exception {
- BroadcastReceiver crashCountReceiver = null;
- Context context = InstrumentationRegistry.getContext();
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
-
- try {
- // Crash TEST_APP_A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
- crashCountReceiver = RollbackTestUtils.sendCrashBroadcast(context, TEST_APP_A, 5);
- } finally {
- if (crashCountReceiver != null) {
- context.unregisterReceiver(crashCountReceiver);
- }
- }
+ // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
+ RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
// We expect the device to be rebooted automatically. Wait for that to
// happen. At that point, the host test driver will wait for the
@@ -153,48 +151,80 @@ public class StagedRollbackTest {
*/
@Test
public void testBadApkOnlyConfirmRollback() throws Exception {
- assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
- RollbackTestUtils.processUserData(TEST_APP_A);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
- rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
- assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback, new VersionedPackage(TEST_APP_A, 2));
- assertTrue(rollback.isStaged());
- assertNotEquals(-1, rollback.getCommittedSessionId());
+ rm.getRecentlyCommittedRollbacks(), TestApp.A);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(rollback).causePackagesContainsExactly(TestApp.ACrashing2);
+ assertThat(rollback).isStaged();
+ assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1);
}
@Test
public void resetNetworkStack() throws Exception {
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
String networkStack = getNetworkStackPackageName();
rm.expireRollbackForPackage(networkStack);
- RollbackTestUtils.uninstall(networkStack);
+ Uninstall.packages(networkStack);
+
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ networkStack)).isNull();
+ }
+
+ @Test
+ public void installModuleMetadataPackage() throws Exception {
+ resetModuleMetadataPackage();
+ Context context = InstrumentationRegistry.getContext();
+ PackageInfo metadataPackageInfo = context.getPackageManager().getPackageInfo(
+ MODULE_META_DATA_PACKAGE, 0);
+ String metadataApkPath = metadataPackageInfo.applicationInfo.sourceDir;
+ assertThat(metadataApkPath).isNotNull();
+ assertThat(metadataApkPath).isNotEqualTo("");
- assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
- networkStack));
+ runShellCommand("pm install "
+ + "-r --enable-rollback --staged --wait "
+ + metadataApkPath);
}
@Test
public void assertNetworkStackRollbackAvailable() throws Exception {
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
- assertNotNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
- getNetworkStackPackageName()));
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ getNetworkStackPackageName())).isNotNull();
}
@Test
public void assertNetworkStackRollbackCommitted() throws Exception {
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
- assertNotNull(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
- getNetworkStackPackageName()));
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ getNetworkStackPackageName())).isNotNull();
}
@Test
public void assertNoNetworkStackRollbackCommitted() throws Exception {
- RollbackManager rm = RollbackTestUtils.getRollbackManager();
- assertNull(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
- getNetworkStackPackageName()));
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ getNetworkStackPackageName())).isNull();
+ }
+
+ @Test
+ public void assertModuleMetadataRollbackAvailable() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ MODULE_META_DATA_PACKAGE)).isNotNull();
+ }
+
+ @Test
+ public void assertModuleMetadataRollbackCommitted() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ MODULE_META_DATA_PACKAGE)).isNotNull();
}
private String getNetworkStackPackageName() {
@@ -203,4 +233,65 @@ public class StagedRollbackTest {
InstrumentationRegistry.getContext().getPackageManager(), 0);
return comp.getPackageName();
}
+
+ @Test
+ public void testPreviouslyAbandonedRollbacksEnableRollback() throws Exception {
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+ int sessionId = Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
+ PackageInstaller pi = InstrumentationRegistry.getContext().getPackageManager()
+ .getPackageInstaller();
+ pi.abandonSession(sessionId);
+
+ // Remove the first intent sender result, so that the next staged install session does not
+ // erroneously think that it has itself been abandoned.
+ // TODO(b/136260017): Restructure LocalIntentSender to negate the need for this step.
+ LocalIntentSender.getIntentSenderResult();
+ Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
+ }
+
+ @Test
+ public void testPreviouslyAbandonedRollbacksCommitRollback() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ InstallUtils.processUserData(TestApp.A);
+
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TestApp.A);
+ RollbackUtils.rollback(rollback.getRollbackId());
+ }
+
+ @Test
+ public void testPreviouslyAbandonedRollbacksCheckUserdataRollback() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+ Uninstall.packages(TestApp.A);
+ }
+
+ @Nullable
+ private static String getModuleMetadataPackageName() {
+ String packageName = InstrumentationRegistry.getContext().getResources().getString(
+ R.string.config_defaultModuleMetadataProvider);
+ if (TextUtils.isEmpty(packageName)) {
+ return null;
+ }
+ return packageName;
+ }
+
+ private void resetModuleMetadataPackage() {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+
+ assertThat(MODULE_META_DATA_PACKAGE).isNotNull();
+ rm.expireRollbackForPackage(MODULE_META_DATA_PACKAGE);
+
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ MODULE_META_DATA_PACKAGE)).isNull();
+ }
+
+ private void runShellCommand(String cmd) {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .executeShellCommand(cmd);
+ }
}
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 4315514cb7a5..bc98f06ebc56 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -25,15 +25,18 @@ import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.TimeUnit;
+
/**
* Runs the staged rollback tests.
*/
@RunWith(DeviceJUnit4ClassRunner.class)
public class StagedRollbackTest extends BaseHostJUnit4Test {
+ private static final int NATIVE_CRASHES_THRESHOLD = 5;
+
/**
* Runs the given phase of a test by calling into the device.
* Throws an exception if the test phase fails.
@@ -64,7 +67,6 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
* Tests watchdog triggered staged rollbacks involving only apks.
*/
@Test
- @Ignore("b/139175593 flaky test")
public void testBadApkOnly() throws Exception {
runPhase("testBadApkOnlyEnableRollback");
getDevice().reboot();
@@ -85,6 +87,35 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
runPhase("testBadApkOnlyConfirmRollback");
}
+ @Test
+ public void testNativeWatchdogTriggersRollback() throws Exception {
+ //Stage install ModuleMetadata package - this simulates a Mainline module update
+ runPhase("installModuleMetadataPackage");
+
+ // Reboot device to activate staged package
+ getDevice().reboot();
+ getDevice().waitForDeviceAvailable();
+
+ runPhase("assertModuleMetadataRollbackAvailable");
+
+ // crash system_server enough times to trigger a rollback
+ crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
+
+ // Rollback should be committed automatically now.
+ // Give time for rollback to be committed. This could take a while,
+ // because we need all of the following to happen:
+ // 1. system_server comes back up and boot completes.
+ // 2. Rollback health observer detects updatable crashing signal.
+ // 3. Staged rollback session becomes ready.
+ // 4. Device actually reboots.
+ // So we give a generous timeout here.
+ assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+ getDevice().waitForDeviceAvailable();
+
+ // verify rollback committed
+ runPhase("assertModuleMetadataRollbackCommitted");
+ }
+
/**
* Tests failed network health check triggers watchdog staged rollbacks.
*/
@@ -167,4 +198,30 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
// Verify rollback was not executed after health check deadline
runPhase("assertNoNetworkStackRollbackCommitted");
}
+
+ /**
+ * Tests rolling back user data where there are multiple rollbacks for that package.
+ */
+ @Test
+ public void testPreviouslyAbandonedRollbacks() throws Exception {
+ runPhase("testPreviouslyAbandonedRollbacksEnableRollback");
+ getDevice().reboot();
+ runPhase("testPreviouslyAbandonedRollbacksCommitRollback");
+ getDevice().reboot();
+ runPhase("testPreviouslyAbandonedRollbacksCheckUserdataRollback");
+ }
+
+ private void crashProcess(String processName, int numberOfCrashes) throws Exception {
+ String pid = "";
+ String lastPid = "invalid";
+ for (int i = 0; i < numberOfCrashes; ++i) {
+ // This condition makes sure before we kill the process, the process is running AND
+ // the last crash was finished.
+ while ("".equals(pid) || lastPid.equals(pid)) {
+ pid = getDevice().executeShellCommand("pidof " + processName);
+ }
+ getDevice().executeShellCommand("kill " + pid);
+ lastPid = pid;
+ }
+ }
}
diff --git a/tests/RollbackTest/TEST_MAPPING b/tests/RollbackTest/TEST_MAPPING
index 6be93a0a199b..fefde5b4be12 100644
--- a/tests/RollbackTest/TEST_MAPPING
+++ b/tests/RollbackTest/TEST_MAPPING
@@ -5,6 +5,9 @@
},
{
"name": "StagedRollbackTest"
+ },
+ {
+ "name": "MultiUserRollbackTest"
}
]
}
diff --git a/tests/RollbackTest/TestApp/ACrashingV2.xml b/tests/RollbackTest/TestApp/ACrashingV2.xml
deleted file mode 100644
index 77bfd4e0f9a0..000000000000
--- a/tests/RollbackTest/TestApp/ACrashingV2.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.rollback.testapp.A"
- android:versionCode="2"
- android:versionName="2.0" >
-
-
- <uses-sdk android:minSdkVersion="19" />
-
- <application android:label="Rollback Test App A v2">
- <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
- android:exported="true" />
- <activity android:name="com.android.tests.rollback.testapp.CrashingMainActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT"/>
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/tests/RollbackTest/TestApp/Av1.xml b/tests/RollbackTest/TestApp/Av1.xml
deleted file mode 100644
index 63729fbaaf28..000000000000
--- a/tests/RollbackTest/TestApp/Av1.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.rollback.testapp.A"
- android:versionCode="1"
- android:versionName="1.0" >
-
-
- <uses-sdk android:minSdkVersion="19" />
-
- <application android:label="Rollback Test App A v1">
- <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
- android:exported="true" />
- <activity android:name="com.android.tests.rollback.testapp.MainActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/tests/RollbackTest/TestApp/Av2.xml b/tests/RollbackTest/TestApp/Av2.xml
deleted file mode 100644
index f0e909feabf3..000000000000
--- a/tests/RollbackTest/TestApp/Av2.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.rollback.testapp.A"
- android:versionCode="2"
- android:versionName="2.0" >
-
-
- <uses-sdk android:minSdkVersion="19" />
-
- <application android:label="Rollback Test App A v2">
- <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
- android:exported="true" />
- <activity android:name="com.android.tests.rollback.testapp.MainActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/tests/RollbackTest/TestApp/Bv1.xml b/tests/RollbackTest/TestApp/Bv1.xml
deleted file mode 100644
index ca9c2ec47a20..000000000000
--- a/tests/RollbackTest/TestApp/Bv1.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.rollback.testapp.B"
- android:versionCode="1"
- android:versionName="1.0" >
-
-
- <uses-sdk android:minSdkVersion="19" />
-
- <application android:label="Rollback Test App B v1">
- <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
- android:exported="true" />
- <activity android:name="com.android.tests.rollback.testapp.MainActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/tests/RollbackTest/TestApp/Bv2.xml b/tests/RollbackTest/TestApp/Bv2.xml
deleted file mode 100644
index bd3e6133f6f6..000000000000
--- a/tests/RollbackTest/TestApp/Bv2.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.rollback.testapp.B"
- android:versionCode="2"
- android:versionName="2.0" >
-
-
- <uses-sdk android:minSdkVersion="19" />
-
- <application android:label="Rollback Test App B v2">
- <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
- android:exported="true" />
- <activity android:name="com.android.tests.rollback.testapp.MainActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/tests/RollbackTest/TestApp/res_v2/values/values.xml b/tests/RollbackTest/TestApp/res_v2/values/values.xml
deleted file mode 100644
index fd988f597f61..000000000000
--- a/tests/RollbackTest/TestApp/res_v2/values/values.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<resources>
- <integer name="app_version">2</integer>
- <integer name="split_version">0</integer>
-</resources>
diff --git a/tests/RollbackTest/TestApp/res_v3/values-anydpi/values.xml b/tests/RollbackTest/TestApp/res_v3/values-anydpi/values.xml
deleted file mode 100644
index f2d8992bee37..000000000000
--- a/tests/RollbackTest/TestApp/res_v3/values-anydpi/values.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<resources>
- <integer name="split_version">3</integer>
-</resources>
diff --git a/tests/RollbackTest/TestApp/res_v3/values/values.xml b/tests/RollbackTest/TestApp/res_v3/values/values.xml
deleted file mode 100644
index 968168a4bf06..000000000000
--- a/tests/RollbackTest/TestApp/res_v3/values/values.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<resources>
- <integer name="app_version">3</integer>
- <integer name="split_version">0</integer>
-</resources>
diff --git a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java
deleted file mode 100644
index 97958acde21b..000000000000
--- a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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 com.android.tests.rollback.testapp;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-
-/**
- * A crashing test app for testing apk rollback support.
- */
-public class CrashingMainActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- incrementCountAndBroadcast();
- throw new RuntimeException("Intended force crash");
- }
-
- private void incrementCountAndBroadcast() {
- SharedPreferences preferences = getSharedPreferences("prefs", Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = preferences.edit();
- int count = preferences.getInt("crash_count", 0);
- editor.putInt("crash_count", ++count).commit();
-
- Intent intent = new Intent("com.android.tests.rollback.CRASH");
- intent.putExtra("count", count);
- sendBroadcast(intent);
- }
-}
diff --git a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/ProcessUserData.java b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/ProcessUserData.java
deleted file mode 100644
index 38c658e795aa..000000000000
--- a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/ProcessUserData.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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 com.android.tests.rollback.testapp;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Scanner;
-
-/**
- * A broadcast reciever to check for and update user app data version
- * compatibility.
- */
-public class ProcessUserData extends BroadcastReceiver {
-
- private static final String TAG = "RollbackTestApp";
-
- /**
- * Exception thrown in case of issue with user data.
- */
- public static class UserDataException extends Exception {
- public UserDataException(String message) {
- super(message);
- }
-
- public UserDataException(String message, Throwable cause) {
- super(message, cause);
- }
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- try {
- processUserData(context);
- setResultCode(1);
- } catch (UserDataException e) {
- setResultCode(0);
- setResultData(e.getMessage());
- }
- }
-
- /**
- * Update the app's user data version to match the app version.
- *
- * @param context The application context.
- * @throws UserDataException in case of problems with app user data.
- */
- public void processUserData(Context context) throws UserDataException {
- Resources res = context.getResources();
- String packageName = context.getPackageName();
-
- int appVersionId = res.getIdentifier("app_version", "integer", packageName);
- int appVersion = res.getInteger(appVersionId);
-
- int splitVersionId = res.getIdentifier("split_version", "integer", packageName);
- int splitVersion = res.getInteger(splitVersionId);
-
- // Make sure the app version and split versions are compatible.
- if (appVersion != splitVersion) {
- throw new UserDataException("Split version " + splitVersion
- + " does not match app version " + appVersion);
- }
-
- // Read the version of the app's user data and ensure it is compatible
- // with our version of the application.
- File versionFile = new File(context.getFilesDir(), "version.txt");
- try {
- Scanner s = new Scanner(versionFile);
- int userDataVersion = s.nextInt();
- s.close();
-
- if (userDataVersion > appVersion) {
- throw new UserDataException("User data is from version " + userDataVersion
- + ", which is not compatible with this version " + appVersion
- + " of the RollbackTestApp");
- }
- } catch (FileNotFoundException e) {
- // No problem. This is a fresh install of the app or the user data
- // has been wiped.
- }
-
- // Record the current version of the app in the user data.
- try {
- PrintWriter pw = new PrintWriter(versionFile);
- pw.println(appVersion);
- pw.close();
- } catch (IOException e) {
- throw new UserDataException("Unable to write user data.", e);
- }
- }
-}
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index ca1eb705e457..ef973acf763b 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -63,6 +63,8 @@ public class UsbHandlerTest {
@Mock
private UsbSettingsManager mUsbSettingsManager;
@Mock
+ private UsbPermissionManager mUsbPermissionManager;
+ @Mock
private SharedPreferences mSharedPreferences;
@Mock
private SharedPreferences.Editor mEditor;
@@ -87,8 +89,9 @@ public class UsbHandlerTest {
Intent mBroadcastedIntent;
MockUsbHandler(Looper looper, Context context, UsbDeviceManager deviceManager,
- UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) {
- super(looper, context, deviceManager, alsaManager, settingsManager);
+ UsbAlsaManager alsaManager, UsbSettingsManager settingsManager,
+ UsbPermissionManager permissionManager) {
+ super(looper, context, deviceManager, alsaManager, permissionManager);
mUseUsbNotification = false;
mIsUsbTransferAllowed = true;
mCurrentUsbFunctionsReceived = true;
@@ -142,7 +145,7 @@ public class UsbHandlerTest {
mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbAlsaManager,
- mUsbSettingsManager);
+ mUsbSettingsManager, mUsbPermissionManager);
}
@SmallTest
@@ -205,7 +208,7 @@ public class UsbHandlerTest {
UsbManager.USB_FUNCTION_ADB);
mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbAlsaManager,
- mUsbSettingsManager);
+ mUsbSettingsManager, mUsbPermissionManager);
sendBootCompleteMessages(mUsbHandler);
mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_ENABLE_ADB, 0));
@@ -228,7 +231,7 @@ public class UsbHandlerTest {
mMockProperties.put(UsbDeviceManager.UsbHandler.USB_PERSISTENT_CONFIG_PROPERTY, "adb");
mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbAlsaManager,
- mUsbSettingsManager);
+ mUsbSettingsManager, mUsbPermissionManager);
sendBootCompleteMessages(mUsbHandler);
assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
@@ -316,7 +319,7 @@ public class UsbHandlerTest {
.thenReturn(UsbManager.USB_FUNCTION_MTP);
mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbAlsaManager,
- mUsbSettingsManager);
+ mUsbSettingsManager, mUsbPermissionManager);
sendBootCompleteMessages(mUsbHandler);
mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_SCREEN_LOCK, 1));
mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_SCREEN_LOCK, 0));
diff --git a/tests/WindowlessWmTest/Android.bp b/tests/WindowlessWmTest/Android.bp
new file mode 100644
index 000000000000..2ace3f363ef9
--- /dev/null
+++ b/tests/WindowlessWmTest/Android.bp
@@ -0,0 +1,22 @@
+//
+// 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.
+//
+
+android_test {
+ name: "WindowlessWmTest",
+ srcs: ["**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/tests/RollbackTest/TestApp/Av3.xml b/tests/WindowlessWmTest/AndroidManifest.xml
index 9725c9f7cf9e..babfd76d91e8 100644
--- a/tests/RollbackTest/TestApp/Av3.xml
+++ b/tests/WindowlessWmTest/AndroidManifest.xml
@@ -1,12 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- 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.
@@ -15,21 +13,16 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tests.rollback.testapp.A"
- android:versionCode="3"
- android:versionName="3.0" >
-
+ package="com.android.test.viewembed">
- <uses-sdk android:minSdkVersion="19" />
-
- <application android:label="Rollback Test App A v3">
- <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
- android:exported="true" />
- <activity android:name="com.android.tests.rollback.testapp.MainActivity">
+ <application>
+ <activity android:name="WindowlessWmTest" android:label="View Embedding Test">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
+
+
</manifest>
diff --git a/tests/WindowlessWmTest/src/com/android/test/viewembed/WindowlessWmTest.java b/tests/WindowlessWmTest/src/com/android/test/viewembed/WindowlessWmTest.java
new file mode 100644
index 000000000000..5a146da1a3f2
--- /dev/null
+++ b/tests/WindowlessWmTest/src/com/android/test/viewembed/WindowlessWmTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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 com.android.test.viewembed;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.view.Gravity;
+import android.view.WindowlessViewRoot;
+import android.view.SurfaceHolder;
+import android.view.SurfaceHolder.Callback;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+
+public class WindowlessWmTest extends Activity implements SurfaceHolder.Callback{
+ SurfaceView mView;
+ WindowlessViewRoot mVr;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ FrameLayout content = new FrameLayout(this);
+ super.onCreate(savedInstanceState);
+ mView = new SurfaceView(this);
+ content.addView(mView, new FrameLayout.LayoutParams(
+ 500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
+ setContentView(content);
+
+ mView.setZOrderOnTop(true);
+ mView.getHolder().addCallback(this);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mVr = new WindowlessViewRoot(this, this.getDisplay(),
+ mView.getSurfaceControl());
+ Button v = new Button(this);
+ v.setBackgroundColor(Color.BLUE);
+ v.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ v.setBackgroundColor(Color.RED);
+ }
+ });
+ WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(500, 500, WindowManager.LayoutParams.TYPE_APPLICATION,
+ 0, PixelFormat.OPAQUE);
+ mVr.addView(v, lp);
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Canvas canvas = holder.lockCanvas();
+ canvas.drawColor(Color.GREEN);
+ holder.unlockCanvasAndPost(canvas);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+}
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index a49eda3d86d0..01bd47b9c608 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -210,33 +210,36 @@ public class TestLooper {
/**
* Run method for the auto dispatch thread.
* The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops.
- * The thread continues looping and attempting to dispatch all messages until at
- * least one message has been dispatched.
+ * The thread continues looping and attempting to dispatch all messages until
+ * {@link #stopAutoDispatch()} has been invoked.
*/
@Override
public void run() {
int dispatchCount = 0;
for (int i = 0; i < MAX_LOOPS; i++) {
try {
- dispatchCount = dispatchAll();
+ dispatchCount += dispatchAll();
} catch (RuntimeException e) {
mAutoDispatchException = e;
- }
- Log.d(TAG, "dispatched " + dispatchCount + " messages");
- if (dispatchCount > 0) {
return;
}
+ Log.d(TAG, "dispatched " + dispatchCount + " messages");
try {
Thread.sleep(LOOP_SLEEP_TIME_MS);
} catch (InterruptedException e) {
- mAutoDispatchException = new IllegalStateException(
- "stopAutoDispatch called before any messages were dispatched.");
+ if (dispatchCount == 0) {
+ Log.e(TAG, "stopAutoDispatch called before any messages were dispatched.");
+ mAutoDispatchException = new IllegalStateException(
+ "stopAutoDispatch called before any messages were dispatched.");
+ }
return;
}
}
- Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
- mAutoDispatchException = new IllegalStateException(
- "TestLooper did not dispatch any messages before exiting.");
+ if (dispatchCount == 0) {
+ Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
+ mAutoDispatchException = new IllegalStateException(
+ "TestLooper did not dispatch any messages before exiting.");
+ }
}
/**
@@ -287,4 +290,17 @@ public class TestLooper {
"stopAutoDispatch called without startAutoDispatch.");
}
}
+
+ /**
+ * If an AutoDispatchThread is currently running, stop and clean up.
+ * This method ignores exceptions raised for indicating that no messages were dispatched.
+ */
+ public void stopAutoDispatchAndIgnoreExceptions() {
+ try {
+ stopAutoDispatch();
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "stopAutoDispatch", e);
+ }
+
+ }
}
diff --git a/tests/utils/testutils/java/android/view/test/InsetsModeSession.java b/tests/utils/testutils/java/android/view/test/InsetsModeSession.java
index c83dfa41d260..e05fdce0ca0c 100644
--- a/tests/utils/testutils/java/android/view/test/InsetsModeSession.java
+++ b/tests/utils/testutils/java/android/view/test/InsetsModeSession.java
@@ -31,7 +31,7 @@ public class InsetsModeSession implements AutoCloseable {
}
@Override
- public void close() throws Exception {
+ public void close() {
ViewRootImpl.sNewInsetsMode = mOldMode;
}
}