Merge changes from topic "flicker-move"
* changes: Ignore flicker tests with open bugs Don't remove flicker test logs from device for easier debugging Reduce delays on Flicker test rotation Make OpenAppCold/Warm tests compatible with AOSP Make Flicker Tests compatible with current AOSP
This commit is contained in:
@@ -23,6 +23,8 @@
|
|||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<!-- Capture screen contents -->
|
<!-- Capture screen contents -->
|
||||||
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
|
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
|
||||||
|
<!-- Enable / Disable tracing !-->
|
||||||
|
<uses-permission android:name="android.permission.DUMP" />
|
||||||
<!-- Run layers trace -->
|
<!-- Run layers trace -->
|
||||||
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
|
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
|
||||||
<application>
|
<application>
|
||||||
@@ -33,4 +35,4 @@
|
|||||||
android:targetPackage="com.android.server.wm.flicker"
|
android:targetPackage="com.android.server.wm.flicker"
|
||||||
android:label="WindowManager Flicker Tests">
|
android:label="WindowManager Flicker Tests">
|
||||||
</instrumentation>
|
</instrumentation>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -25,5 +25,6 @@
|
|||||||
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
|
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
|
||||||
<option name="directory-keys" value="/sdcard/flicker" />
|
<option name="directory-keys" value="/sdcard/flicker" />
|
||||||
<option name="collect-on-run-ended-only" value="true" />
|
<option name="collect-on-run-ended-only" value="true" />
|
||||||
|
<option name="clean-up" value="false" />
|
||||||
</metrics_collector>
|
</metrics_collector>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -1,57 +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.
|
|
||||||
//
|
|
||||||
|
|
||||||
java_test {
|
|
||||||
name: "flickerlib",
|
|
||||||
platform_apis: true,
|
|
||||||
srcs: ["src/**/*.java"],
|
|
||||||
static_libs: [
|
|
||||||
"androidx.test.janktesthelper",
|
|
||||||
"cts-wm-util",
|
|
||||||
"platformprotosnano",
|
|
||||||
"layersprotosnano",
|
|
||||||
"truth-prebuilt",
|
|
||||||
"sysui-helper",
|
|
||||||
"launcher-helper-lib",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
java_library {
|
|
||||||
name: "flickerlib_without_helpers",
|
|
||||||
platform_apis: true,
|
|
||||||
srcs: ["src/**/*.java"],
|
|
||||||
exclude_srcs: ["src/**/helpers/*.java"],
|
|
||||||
static_libs: [
|
|
||||||
"cts-wm-util",
|
|
||||||
"platformprotosnano",
|
|
||||||
"layersprotosnano",
|
|
||||||
"truth-prebuilt"
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
java_library {
|
|
||||||
name: "flickerautomationhelperlib",
|
|
||||||
sdk_version: "test_current",
|
|
||||||
srcs: [
|
|
||||||
"src/com/android/server/wm/flicker/helpers/AutomationUtils.java",
|
|
||||||
"src/com/android/server/wm/flicker/WindowUtils.java",
|
|
||||||
],
|
|
||||||
static_libs: [
|
|
||||||
"sysui-helper",
|
|
||||||
"launcher-helper-lib",
|
|
||||||
"compatibility-device-util-axt",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -1,134 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collection of functional interfaces and classes representing assertions and their associated
|
|
||||||
* results. Assertions are functions that are applied over a single trace entry and returns a
|
|
||||||
* result which includes a detailed reason if the assertion fails.
|
|
||||||
*/
|
|
||||||
public class Assertions {
|
|
||||||
/**
|
|
||||||
* Checks assertion on a single trace entry.
|
|
||||||
*
|
|
||||||
* @param <T> trace entry type to perform the assertion on.
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface TraceAssertion<T> extends Function<T, Result> {
|
|
||||||
/**
|
|
||||||
* Returns an assertion that represents the logical negation of this assertion.
|
|
||||||
*
|
|
||||||
* @return a assertion that represents the logical negation of this assertion
|
|
||||||
*/
|
|
||||||
default TraceAssertion<T> negate() {
|
|
||||||
return (T t) -> apply(t).negate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks assertion on a single layers trace entry.
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface LayersTraceAssertion extends TraceAssertion<LayersTrace.Entry> {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class to store assertions with an identifier to help generate more useful debug
|
|
||||||
* data when dealing with multiple assertions.
|
|
||||||
*/
|
|
||||||
public static class NamedAssertion<T> {
|
|
||||||
public final TraceAssertion<T> assertion;
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
public NamedAssertion(TraceAssertion<T> assertion, String name) {
|
|
||||||
this.assertion = assertion;
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains the result of an assertion including the reason for failed assertions.
|
|
||||||
*/
|
|
||||||
public static class Result {
|
|
||||||
public static final String NEGATION_PREFIX = "!";
|
|
||||||
public final boolean success;
|
|
||||||
public final long timestamp;
|
|
||||||
public final String assertionName;
|
|
||||||
public final String reason;
|
|
||||||
|
|
||||||
public Result(boolean success, long timestamp, String assertionName, String reason) {
|
|
||||||
this.success = success;
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
this.assertionName = assertionName;
|
|
||||||
this.reason = reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Result(boolean success, String reason) {
|
|
||||||
this.success = success;
|
|
||||||
this.reason = reason;
|
|
||||||
this.assertionName = "";
|
|
||||||
this.timestamp = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the negated {@code Result} and adds a negation prefix to the assertion name.
|
|
||||||
*/
|
|
||||||
public Result negate() {
|
|
||||||
String negatedAssertionName;
|
|
||||||
if (this.assertionName.startsWith(NEGATION_PREFIX)) {
|
|
||||||
negatedAssertionName = this.assertionName.substring(NEGATION_PREFIX.length() + 1);
|
|
||||||
} else {
|
|
||||||
negatedAssertionName = NEGATION_PREFIX + this.assertionName;
|
|
||||||
}
|
|
||||||
return new Result(!this.success, this.timestamp, negatedAssertionName, this.reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean passed() {
|
|
||||||
return this.success;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean failed() {
|
|
||||||
return !this.success;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Timestamp: " + prettyTimestamp(timestamp)
|
|
||||||
+ "\nAssertion: " + assertionName
|
|
||||||
+ "\nReason: " + reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String prettyTimestamp(long timestamp_ns) {
|
|
||||||
StringBuilder prettyTimestamp = new StringBuilder();
|
|
||||||
TimeUnit[] timeUnits = {TimeUnit.HOURS, TimeUnit.MINUTES, TimeUnit.SECONDS, TimeUnit
|
|
||||||
.MILLISECONDS};
|
|
||||||
String[] unitSuffixes = {"h", "m", "s", "ms"};
|
|
||||||
|
|
||||||
for (int i = 0; i < timeUnits.length; i++) {
|
|
||||||
long convertedTime = timeUnits[i].convert(timestamp_ns, TimeUnit.NANOSECONDS);
|
|
||||||
timestamp_ns -= TimeUnit.NANOSECONDS.convert(convertedTime, timeUnits[i]);
|
|
||||||
prettyTimestamp.append(convertedTime).append(unitSuffixes[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return prettyTimestamp.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,183 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import com.android.server.wm.flicker.Assertions.NamedAssertion;
|
|
||||||
import com.android.server.wm.flicker.Assertions.Result;
|
|
||||||
import com.android.server.wm.flicker.Assertions.TraceAssertion;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures some of the common logic in {@link LayersTraceSubject} and {@link WmTraceSubject}
|
|
||||||
* used to filter trace entries and combine multiple assertions.
|
|
||||||
*
|
|
||||||
* @param <T> trace entry type
|
|
||||||
*/
|
|
||||||
public class AssertionsChecker<T extends ITraceEntry> {
|
|
||||||
private boolean mFilterEntriesByRange = false;
|
|
||||||
private long mFilterStartTime = 0;
|
|
||||||
private long mFilterEndTime = 0;
|
|
||||||
private AssertionOption mOption = AssertionOption.NONE;
|
|
||||||
private List<NamedAssertion<T>> mAssertions = new LinkedList<>();
|
|
||||||
|
|
||||||
public void add(Assertions.TraceAssertion<T> assertion, String name) {
|
|
||||||
mAssertions.add(new NamedAssertion<>(assertion, name));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void filterByRange(long startTime, long endTime) {
|
|
||||||
mFilterEntriesByRange = true;
|
|
||||||
mFilterStartTime = startTime;
|
|
||||||
mFilterEndTime = endTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setOption(AssertionOption option) {
|
|
||||||
if (mOption != AssertionOption.NONE && option != mOption) {
|
|
||||||
throw new IllegalArgumentException("Cannot use " + mOption + " option with "
|
|
||||||
+ option + " option.");
|
|
||||||
}
|
|
||||||
mOption = option;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkFirstEntry() {
|
|
||||||
setOption(AssertionOption.CHECK_FIRST_ENTRY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkLastEntry() {
|
|
||||||
setOption(AssertionOption.CHECK_LAST_ENTRY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkChangingAssertions() {
|
|
||||||
setOption(AssertionOption.CHECK_CHANGING_ASSERTIONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters trace entries then runs assertions returning a list of failures.
|
|
||||||
*
|
|
||||||
* @param entries list of entries to perform assertions on
|
|
||||||
* @return list of failed assertion results
|
|
||||||
*/
|
|
||||||
public List<Result> test(List<T> entries) {
|
|
||||||
List<T> filteredEntries;
|
|
||||||
List<Result> failures;
|
|
||||||
|
|
||||||
if (mFilterEntriesByRange) {
|
|
||||||
filteredEntries = entries.stream()
|
|
||||||
.filter(e -> ((e.getTimestamp() >= mFilterStartTime)
|
|
||||||
&& (e.getTimestamp() <= mFilterEndTime)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
} else {
|
|
||||||
filteredEntries = entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (mOption) {
|
|
||||||
case CHECK_CHANGING_ASSERTIONS:
|
|
||||||
return assertChanges(filteredEntries);
|
|
||||||
case CHECK_FIRST_ENTRY:
|
|
||||||
return assertEntry(filteredEntries.get(0));
|
|
||||||
case CHECK_LAST_ENTRY:
|
|
||||||
return assertEntry(filteredEntries.get(filteredEntries.size() - 1));
|
|
||||||
}
|
|
||||||
return assertAll(filteredEntries);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Steps through each trace entry checking if provided assertions are true in the order they
|
|
||||||
* are added. Each assertion must be true for at least a single trace entry.
|
|
||||||
*
|
|
||||||
* This can be used to check for asserting a change in property over a trace. Such as visibility
|
|
||||||
* for a window changes from true to false or top-most window changes from A to Bb and back to A
|
|
||||||
* again.
|
|
||||||
*/
|
|
||||||
private List<Result> assertChanges(List<T> entries) {
|
|
||||||
List<Result> failures = new ArrayList<>();
|
|
||||||
int entryIndex = 0;
|
|
||||||
int assertionIndex = 0;
|
|
||||||
int lastPassedAssertionIndex = -1;
|
|
||||||
|
|
||||||
if (mAssertions.size() == 0) {
|
|
||||||
return failures;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (assertionIndex < mAssertions.size() && entryIndex < entries.size()) {
|
|
||||||
TraceAssertion<T> currentAssertion = mAssertions.get(assertionIndex).assertion;
|
|
||||||
Result result = currentAssertion.apply(entries.get(entryIndex));
|
|
||||||
if (result.passed()) {
|
|
||||||
lastPassedAssertionIndex = assertionIndex;
|
|
||||||
entryIndex++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastPassedAssertionIndex != assertionIndex) {
|
|
||||||
failures.add(result);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
assertionIndex++;
|
|
||||||
|
|
||||||
if (assertionIndex == mAssertions.size()) {
|
|
||||||
failures.add(result);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failures.isEmpty()) {
|
|
||||||
if (assertionIndex != mAssertions.size() - 1) {
|
|
||||||
String reason = "\nAssertion " + mAssertions.get(assertionIndex).name
|
|
||||||
+ " never became false";
|
|
||||||
reason += "\nPassed assertions: " + mAssertions.stream().limit(assertionIndex)
|
|
||||||
.map(assertion -> assertion.name).collect(Collectors.joining(","));
|
|
||||||
reason += "\nUntested assertions: " + mAssertions.stream().skip(assertionIndex + 1)
|
|
||||||
.map(assertion -> assertion.name).collect(Collectors.joining(","));
|
|
||||||
|
|
||||||
Result result = new Result(false /* success */, 0 /* timestamp */,
|
|
||||||
"assertChanges", "Not all assertions passed." + reason);
|
|
||||||
failures.add(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return failures;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Result> assertEntry(T entry) {
|
|
||||||
List<Result> failures = new ArrayList<>();
|
|
||||||
for (NamedAssertion<T> assertion : mAssertions) {
|
|
||||||
Result result = assertion.assertion.apply(entry);
|
|
||||||
if (result.failed()) {
|
|
||||||
failures.add(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return failures;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Result> assertAll(List<T> entries) {
|
|
||||||
return mAssertions.stream().flatMap(
|
|
||||||
assertion -> entries.stream()
|
|
||||||
.map(assertion.assertion)
|
|
||||||
.filter(Result::failed))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum AssertionOption {
|
|
||||||
NONE,
|
|
||||||
CHECK_CHANGING_ASSERTIONS,
|
|
||||||
CHECK_FIRST_ENTRY,
|
|
||||||
CHECK_LAST_ENTRY,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common interface for Layer and WindowManager trace entries.
|
|
||||||
*/
|
|
||||||
public interface ITraceEntry {
|
|
||||||
/**
|
|
||||||
* @return timestamp of current entry
|
|
||||||
*/
|
|
||||||
long getTimestamp();
|
|
||||||
}
|
|
||||||
@@ -1,420 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.surfaceflinger.nano.Layers.LayerProto;
|
|
||||||
import android.surfaceflinger.nano.Layers.RectProto;
|
|
||||||
import android.surfaceflinger.nano.Layers.RegionProto;
|
|
||||||
import android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto;
|
|
||||||
import android.surfaceflinger.nano.Layerstrace.LayersTraceProto;
|
|
||||||
import android.util.SparseArray;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.android.server.wm.flicker.Assertions.Result;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains a collection of parsed Layers trace entries and assertions to apply over
|
|
||||||
* a single entry.
|
|
||||||
*
|
|
||||||
* Each entry is parsed into a list of {@link LayersTrace.Entry} objects.
|
|
||||||
*/
|
|
||||||
public class LayersTrace {
|
|
||||||
final private List<Entry> mEntries;
|
|
||||||
@Nullable
|
|
||||||
final private Path mSource;
|
|
||||||
|
|
||||||
private LayersTrace(List<Entry> entries, Path source) {
|
|
||||||
this.mEntries = entries;
|
|
||||||
this.mSource = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
|
|
||||||
* of trace entries, storing the flattened layers into its hierarchical structure.
|
|
||||||
*
|
|
||||||
* @param data binary proto data
|
|
||||||
* @param source Path to source of data for additional debug information
|
|
||||||
*/
|
|
||||||
public static LayersTrace parseFrom(byte[] data, Path source) {
|
|
||||||
List<Entry> entries = new ArrayList<>();
|
|
||||||
LayersTraceFileProto fileProto;
|
|
||||||
try {
|
|
||||||
fileProto = LayersTraceFileProto.parseFrom(data);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
for (LayersTraceProto traceProto : fileProto.entry) {
|
|
||||||
Entry entry = Entry.fromFlattenedLayers(traceProto.elapsedRealtimeNanos,
|
|
||||||
traceProto.layers.layers);
|
|
||||||
entries.add(entry);
|
|
||||||
}
|
|
||||||
return new LayersTrace(entries, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
|
|
||||||
* of trace entries, storing the flattened layers into its hierarchical structure.
|
|
||||||
*
|
|
||||||
* @param data binary proto data
|
|
||||||
*/
|
|
||||||
public static LayersTrace parseFrom(byte[] data) {
|
|
||||||
return parseFrom(data, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Entry> getEntries() {
|
|
||||||
return mEntries;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entry getEntry(long timestamp) {
|
|
||||||
Optional<Entry> entry = mEntries.stream()
|
|
||||||
.filter(e -> e.getTimestamp() == timestamp)
|
|
||||||
.findFirst();
|
|
||||||
if (!entry.isPresent()) {
|
|
||||||
throw new RuntimeException("Entry does not exist for timestamp " + timestamp);
|
|
||||||
}
|
|
||||||
return entry.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<Path> getSource() {
|
|
||||||
return Optional.ofNullable(mSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a single Layer trace entry.
|
|
||||||
*/
|
|
||||||
public static class Entry implements ITraceEntry {
|
|
||||||
private long mTimestamp;
|
|
||||||
private List<Layer> mRootLayers; // hierarchical representation of layers
|
|
||||||
private List<Layer> mFlattenedLayers = null;
|
|
||||||
|
|
||||||
private Entry(long timestamp, List<Layer> rootLayers) {
|
|
||||||
this.mTimestamp = timestamp;
|
|
||||||
this.mRootLayers = rootLayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs the layer hierarchy from a flattened list of layers.
|
|
||||||
*/
|
|
||||||
public static Entry fromFlattenedLayers(long timestamp, LayerProto[] protos) {
|
|
||||||
SparseArray<Layer> layerMap = new SparseArray<>();
|
|
||||||
ArrayList<Layer> orphans = new ArrayList<>();
|
|
||||||
for (LayerProto proto : protos) {
|
|
||||||
int id = proto.id;
|
|
||||||
int parentId = proto.parent;
|
|
||||||
|
|
||||||
Layer newLayer = layerMap.get(id);
|
|
||||||
if (newLayer == null) {
|
|
||||||
newLayer = new Layer(proto);
|
|
||||||
layerMap.append(id, newLayer);
|
|
||||||
} else if (newLayer.mProto != null) {
|
|
||||||
throw new RuntimeException("Duplicate layer id found:" + id);
|
|
||||||
} else {
|
|
||||||
newLayer.mProto = proto;
|
|
||||||
orphans.remove(newLayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add parent placeholder
|
|
||||||
if (layerMap.get(parentId) == null) {
|
|
||||||
Layer orphanLayer = new Layer(null);
|
|
||||||
layerMap.append(parentId, orphanLayer);
|
|
||||||
orphans.add(orphanLayer);
|
|
||||||
}
|
|
||||||
layerMap.get(parentId).addChild(newLayer);
|
|
||||||
newLayer.addParent(layerMap.get(parentId));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail if we find orphan layers.
|
|
||||||
orphans.remove(layerMap.get(-1));
|
|
||||||
orphans.forEach(orphan -> {
|
|
||||||
String childNodes = orphan.mChildren.stream().map(node ->
|
|
||||||
Integer.toString(node.getId())).collect(Collectors.joining(", "));
|
|
||||||
int orphanId = orphan.mChildren.get(0).mProto.parent;
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Failed to parse layers trace. Found orphan layers with parent "
|
|
||||||
+ "layer id:" + orphanId + " : " + childNodes);
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Entry(timestamp, layerMap.get(-1).mChildren);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts {@link Rect} from {@link RectProto}.
|
|
||||||
*/
|
|
||||||
private static Rect extract(RectProto proto) {
|
|
||||||
return new Rect(proto.left, proto.top, proto.right, proto.bottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts {@link Rect} from {@link RegionProto} by returning a rect that encompasses all
|
|
||||||
* the rects making up the region.
|
|
||||||
*/
|
|
||||||
private static Rect extract(RegionProto regionProto) {
|
|
||||||
Rect region = new Rect();
|
|
||||||
for (RectProto proto : regionProto.rect) {
|
|
||||||
region.union(proto.left, proto.top, proto.right, proto.bottom);
|
|
||||||
}
|
|
||||||
return region;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a region specified by {@code testRect} is covered by all visible layers.
|
|
||||||
*/
|
|
||||||
public Result coversRegion(Rect testRect) {
|
|
||||||
String assertionName = "coversRegion";
|
|
||||||
Collection<Layer> layers = asFlattenedLayers();
|
|
||||||
|
|
||||||
for (int x = testRect.left; x < testRect.right; x++) {
|
|
||||||
for (int y = testRect.top; y < testRect.bottom; y++) {
|
|
||||||
boolean emptyRegionFound = true;
|
|
||||||
for (Layer layer : layers) {
|
|
||||||
if (layer.isInvisible() || layer.isHiddenByParent()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (RectProto rectProto : layer.mProto.visibleRegion.rect) {
|
|
||||||
Rect r = extract(rectProto);
|
|
||||||
if (r.contains(x, y)) {
|
|
||||||
y = r.bottom;
|
|
||||||
emptyRegionFound = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (emptyRegionFound) {
|
|
||||||
String reason = "Region to test: " + testRect
|
|
||||||
+ "\nfirst empty point: " + x + ", " + y;
|
|
||||||
reason += "\nvisible regions:";
|
|
||||||
for (Layer layer : layers) {
|
|
||||||
if (layer.isInvisible() || layer.isHiddenByParent()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Rect r = extract(layer.mProto.visibleRegion);
|
|
||||||
reason += "\n" + layer.mProto.name + r.toString();
|
|
||||||
}
|
|
||||||
return new Result(false /* success */, this.mTimestamp, assertionName,
|
|
||||||
reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String info = "Region covered: " + testRect;
|
|
||||||
return new Result(true /* success */, this.mTimestamp, assertionName, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a layer with name {@code layerName} has a visible region
|
|
||||||
* {@code expectedVisibleRegion}.
|
|
||||||
*/
|
|
||||||
public Result hasVisibleRegion(String layerName, Rect expectedVisibleRegion) {
|
|
||||||
String assertionName = "hasVisibleRegion";
|
|
||||||
String reason = "Could not find " + layerName;
|
|
||||||
for (Layer layer : asFlattenedLayers()) {
|
|
||||||
if (layer.mProto.name.contains(layerName)) {
|
|
||||||
if (layer.isHiddenByParent()) {
|
|
||||||
reason = layer.getHiddenByParentReason();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (layer.isInvisible()) {
|
|
||||||
reason = layer.getVisibilityReason();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Rect visibleRegion = extract(layer.mProto.visibleRegion);
|
|
||||||
if (visibleRegion.equals(expectedVisibleRegion)) {
|
|
||||||
return new Result(true /* success */, this.mTimestamp, assertionName,
|
|
||||||
layer.mProto.name + "has visible region " + expectedVisibleRegion);
|
|
||||||
}
|
|
||||||
reason = layer.mProto.name + " has visible region:" + visibleRegion + " "
|
|
||||||
+ "expected:" + expectedVisibleRegion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Result(false /* success */, this.mTimestamp, assertionName, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a layer with name {@code layerName} is visible.
|
|
||||||
*/
|
|
||||||
public Result isVisible(String layerName) {
|
|
||||||
String assertionName = "isVisible";
|
|
||||||
String reason = "Could not find " + layerName;
|
|
||||||
for (Layer layer : asFlattenedLayers()) {
|
|
||||||
if (layer.mProto.name.contains(layerName)) {
|
|
||||||
if (layer.isHiddenByParent()) {
|
|
||||||
reason = layer.getHiddenByParentReason();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (layer.isInvisible()) {
|
|
||||||
reason = layer.getVisibilityReason();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return new Result(true /* success */, this.mTimestamp, assertionName,
|
|
||||||
layer.mProto.name + " is visible");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Result(false /* success */, this.mTimestamp, assertionName, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getTimestamp() {
|
|
||||||
return mTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Layer> getRootLayers() {
|
|
||||||
return mRootLayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all layers as a flattened list using a depth first traversal.
|
|
||||||
*/
|
|
||||||
public List<Layer> asFlattenedLayers() {
|
|
||||||
if (mFlattenedLayers == null) {
|
|
||||||
mFlattenedLayers = new LinkedList<>();
|
|
||||||
ArrayList<Layer> pendingLayers = new ArrayList<>(this.mRootLayers);
|
|
||||||
while (!pendingLayers.isEmpty()) {
|
|
||||||
Layer layer = pendingLayers.remove(0);
|
|
||||||
mFlattenedLayers.add(layer);
|
|
||||||
pendingLayers.addAll(0, layer.mChildren);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mFlattenedLayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Rect getVisibleBounds(String layerName) {
|
|
||||||
List<Layer> layers = asFlattenedLayers();
|
|
||||||
for (Layer layer : layers) {
|
|
||||||
if (layer.mProto.name.contains(layerName) && layer.isVisible()) {
|
|
||||||
return extract(layer.mProto.visibleRegion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Rect(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a single layer with links to its parent and child layers.
|
|
||||||
*/
|
|
||||||
public static class Layer {
|
|
||||||
@Nullable
|
|
||||||
public LayerProto mProto;
|
|
||||||
public List<Layer> mChildren;
|
|
||||||
@Nullable
|
|
||||||
public Layer mParent = null;
|
|
||||||
|
|
||||||
private Layer(LayerProto proto) {
|
|
||||||
this.mProto = proto;
|
|
||||||
this.mChildren = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addChild(Layer childLayer) {
|
|
||||||
this.mChildren.add(childLayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addParent(Layer parentLayer) {
|
|
||||||
this.mParent = parentLayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId() {
|
|
||||||
return mProto.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isActiveBufferEmpty() {
|
|
||||||
return this.mProto.activeBuffer == null || this.mProto.activeBuffer.height == 0
|
|
||||||
|| this.mProto.activeBuffer.width == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVisibleRegionEmpty() {
|
|
||||||
if (this.mProto.visibleRegion == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Rect visibleRect = Entry.extract(this.mProto.visibleRegion);
|
|
||||||
return visibleRect.height() == 0 || visibleRect.width() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHidden() {
|
|
||||||
return (this.mProto.flags & /* FLAG_HIDDEN */ 0x1) != 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVisible() {
|
|
||||||
return (!isActiveBufferEmpty() || isColorLayer())
|
|
||||||
&& !isHidden()
|
|
||||||
&& this.mProto.color != null
|
|
||||||
&& this.mProto.color.a > 0
|
|
||||||
&& !isVisibleRegionEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isColorLayer() {
|
|
||||||
return this.mProto.type.equals("ColorLayer");
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRootLayer() {
|
|
||||||
return mParent == null || mParent.mProto == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInvisible() {
|
|
||||||
return !isVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHiddenByParent() {
|
|
||||||
return !isRootLayer() && (mParent.isHidden() || mParent.isHiddenByParent());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHiddenByParentReason() {
|
|
||||||
String reason = "Layer " + mProto.name;
|
|
||||||
if (isHiddenByParent()) {
|
|
||||||
reason += " is hidden by parent: " + mParent.mProto.name;
|
|
||||||
} else {
|
|
||||||
reason += " is not hidden by parent: " + mParent.mProto.name;
|
|
||||||
}
|
|
||||||
return reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVisibilityReason() {
|
|
||||||
String reason = "Layer " + mProto.name;
|
|
||||||
if (isVisible()) {
|
|
||||||
reason += " is visible:";
|
|
||||||
} else {
|
|
||||||
reason += " is invisible:";
|
|
||||||
if (this.mProto.activeBuffer == null) {
|
|
||||||
reason += " activeBuffer=null";
|
|
||||||
} else if (this.mProto.activeBuffer.height == 0) {
|
|
||||||
reason += " activeBuffer.height=0";
|
|
||||||
} else if (this.mProto.activeBuffer.width == 0) {
|
|
||||||
reason += " activeBuffer.width=0";
|
|
||||||
}
|
|
||||||
if (!isColorLayer()) {
|
|
||||||
reason += " type != ColorLayer";
|
|
||||||
}
|
|
||||||
if (isHidden()) {
|
|
||||||
reason += " flags=" + this.mProto.flags + " (FLAG_HIDDEN set)";
|
|
||||||
}
|
|
||||||
if (this.mProto.color == null || this.mProto.color.a == 0) {
|
|
||||||
reason += " color.a=0";
|
|
||||||
}
|
|
||||||
if (isVisibleRegionEmpty()) {
|
|
||||||
reason += " visible region is empty";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reason;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertAbout;
|
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
|
||||||
|
|
||||||
import android.graphics.Rect;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.android.server.wm.flicker.Assertions.Result;
|
|
||||||
import com.android.server.wm.flicker.LayersTrace.Entry;
|
|
||||||
import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
|
|
||||||
|
|
||||||
import com.google.common.truth.FailureMetadata;
|
|
||||||
import com.google.common.truth.Subject;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Truth subject for {@link LayersTrace} objects.
|
|
||||||
*/
|
|
||||||
public class LayersTraceSubject extends Subject<LayersTraceSubject, LayersTrace> {
|
|
||||||
// Boiler-plate Subject.Factory for LayersTraceSubject
|
|
||||||
private static final Subject.Factory<LayersTraceSubject, LayersTrace> FACTORY =
|
|
||||||
new Subject.Factory<LayersTraceSubject, LayersTrace>() {
|
|
||||||
@Override
|
|
||||||
public LayersTraceSubject createSubject(
|
|
||||||
FailureMetadata fm, @Nullable LayersTrace target) {
|
|
||||||
return new LayersTraceSubject(fm, target);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private AssertionsChecker<Entry> mChecker = new AssertionsChecker<>();
|
|
||||||
|
|
||||||
private LayersTraceSubject(FailureMetadata fm, @Nullable LayersTrace subject) {
|
|
||||||
super(fm, subject);
|
|
||||||
}
|
|
||||||
|
|
||||||
// User-defined entry point
|
|
||||||
public static LayersTraceSubject assertThat(@Nullable LayersTrace entry) {
|
|
||||||
return assertAbout(FACTORY).that(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
// User-defined entry point
|
|
||||||
public static LayersTraceSubject assertThat(@Nullable TransitionResult result) {
|
|
||||||
LayersTrace entries = LayersTrace.parseFrom(result.getLayersTrace(),
|
|
||||||
result.getLayersTracePath());
|
|
||||||
return assertWithMessage(result.toString()).about(FACTORY).that(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Static method for getting the subject factory (for use with assertAbout())
|
|
||||||
public static Subject.Factory<LayersTraceSubject, LayersTrace> entries() {
|
|
||||||
return FACTORY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void forAllEntries() {
|
|
||||||
test();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void forRange(long startTime, long endTime) {
|
|
||||||
mChecker.filterByRange(startTime, endTime);
|
|
||||||
test();
|
|
||||||
}
|
|
||||||
|
|
||||||
public LayersTraceSubject then() {
|
|
||||||
mChecker.checkChangingAssertions();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void inTheBeginning() {
|
|
||||||
if (getSubject().getEntries().isEmpty()) {
|
|
||||||
fail("No entries found.");
|
|
||||||
}
|
|
||||||
mChecker.checkFirstEntry();
|
|
||||||
test();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void atTheEnd() {
|
|
||||||
if (getSubject().getEntries().isEmpty()) {
|
|
||||||
fail("No entries found.");
|
|
||||||
}
|
|
||||||
mChecker.checkLastEntry();
|
|
||||||
test();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void test() {
|
|
||||||
List<Result> failures = mChecker.test(getSubject().getEntries());
|
|
||||||
if (!failures.isEmpty()) {
|
|
||||||
String failureLogs = failures.stream().map(Result::toString)
|
|
||||||
.collect(Collectors.joining("\n"));
|
|
||||||
String tracePath = "";
|
|
||||||
if (getSubject().getSource().isPresent()) {
|
|
||||||
tracePath = "\nLayers Trace can be found in: "
|
|
||||||
+ getSubject().getSource().get().toAbsolutePath() + "\n";
|
|
||||||
}
|
|
||||||
fail(tracePath + failureLogs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public LayersTraceSubject coversRegion(Rect rect) {
|
|
||||||
mChecker.add(entry -> entry.coversRegion(rect),
|
|
||||||
"coversRegion(" + rect + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LayersTraceSubject hasVisibleRegion(String layerName, Rect size) {
|
|
||||||
mChecker.add(entry -> entry.hasVisibleRegion(layerName, size),
|
|
||||||
"hasVisibleRegion(" + layerName + size + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LayersTraceSubject showsLayer(String layerName) {
|
|
||||||
mChecker.add(entry -> entry.isVisible(layerName),
|
|
||||||
"showsLayer(" + layerName + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LayersTraceSubject hidesLayer(String layerName) {
|
|
||||||
mChecker.add(entry -> entry.isVisible(layerName).negate(),
|
|
||||||
"hidesLayer(" + layerName + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,433 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import static com.android.server.wm.flicker.monitor.ITransitionMonitor.OUTPUT_DIR;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
|
||||||
|
|
||||||
import com.android.server.wm.flicker.monitor.ITransitionMonitor;
|
|
||||||
import com.android.server.wm.flicker.monitor.LayersTraceMonitor;
|
|
||||||
import com.android.server.wm.flicker.monitor.ScreenRecorder;
|
|
||||||
import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor;
|
|
||||||
import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor;
|
|
||||||
|
|
||||||
import com.google.common.io.Files;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds and runs UI transitions capturing test artifacts.
|
|
||||||
*
|
|
||||||
* User can compose a transition from simpler steps, specifying setup and teardown steps. During
|
|
||||||
* a transition, Layers trace, WindowManager trace, screen recordings and window animation frame
|
|
||||||
* stats can be captured.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* Transition builder options:
|
|
||||||
* {@link TransitionBuilder#run(Runnable)} run transition under test. Monitors will be started
|
|
||||||
* before the transition and stopped after the transition is completed.
|
|
||||||
* {@link TransitionBuilder#repeat(int)} repeat transitions under test multiple times recording
|
|
||||||
* result for each run.
|
|
||||||
* {@link TransitionBuilder#withTag(String)} specify a string identifier used to prefix logs and
|
|
||||||
* artifacts generated.
|
|
||||||
* {@link TransitionBuilder#runBeforeAll(Runnable)} run setup transitions once before all other
|
|
||||||
* transition are run to set up an initial state on device.
|
|
||||||
* {@link TransitionBuilder#runBefore(Runnable)} run setup transitions before each test transition
|
|
||||||
* run.
|
|
||||||
* {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions after each test
|
|
||||||
* transition.
|
|
||||||
* {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions once after all
|
|
||||||
* other transition are run.
|
|
||||||
* {@link TransitionBuilder#includeJankyRuns()} disables {@link WindowAnimationFrameStatsMonitor}
|
|
||||||
* to monitor janky frames. If janky frames are detected, then the test run is skipped. This
|
|
||||||
* monitor is enabled by default.
|
|
||||||
* {@link TransitionBuilder#skipLayersTrace()} disables {@link LayersTraceMonitor} used to
|
|
||||||
* capture Layers trace during a transition. This monitor is enabled by default.
|
|
||||||
* {@link TransitionBuilder#skipWindowManagerTrace()} disables {@link WindowManagerTraceMonitor}
|
|
||||||
* used to capture WindowManager trace during a transition. This monitor is enabled by
|
|
||||||
* default.
|
|
||||||
* {@link TransitionBuilder#recordAllRuns()} records the screen contents and saves it to a file.
|
|
||||||
* All the runs including setup and teardown transitions are included in the recording. This
|
|
||||||
* monitor is used for debugging purposes.
|
|
||||||
* {@link TransitionBuilder#recordEachRun()} records the screen contents during test transitions
|
|
||||||
* and saves it to a file for each run. This monitor is used for debugging purposes.
|
|
||||||
*
|
|
||||||
* Example transition to capture WindowManager and Layers trace when opening a test app:
|
|
||||||
* {@code
|
|
||||||
* TransitionRunner.newBuilder()
|
|
||||||
* .withTag("OpenTestAppFast")
|
|
||||||
* .runBeforeAll(UiAutomationLib::wakeUp)
|
|
||||||
* .runBeforeAll(UiAutomationLib::UnlockDevice)
|
|
||||||
* .runBeforeAll(UiAutomationLib::openTestApp)
|
|
||||||
* .runBefore(UiAutomationLib::closeTestApp)
|
|
||||||
* .run(UiAutomationLib::openTestApp)
|
|
||||||
* .runAfterAll(UiAutomationLib::closeTestApp)
|
|
||||||
* .repeat(5)
|
|
||||||
* .build()
|
|
||||||
* .run();
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
public class TransitionRunner {
|
|
||||||
private static final String TAG = "FLICKER";
|
|
||||||
private final ScreenRecorder mScreenRecorder;
|
|
||||||
private final WindowManagerTraceMonitor mWmTraceMonitor;
|
|
||||||
private final LayersTraceMonitor mLayersTraceMonitor;
|
|
||||||
private final WindowAnimationFrameStatsMonitor mFrameStatsMonitor;
|
|
||||||
|
|
||||||
private final List<ITransitionMonitor> mAllRunsMonitors;
|
|
||||||
private final List<ITransitionMonitor> mPerRunMonitors;
|
|
||||||
private final List<Runnable> mBeforeAlls;
|
|
||||||
private final List<Runnable> mBefores;
|
|
||||||
private final List<Runnable> mTransitions;
|
|
||||||
private final List<Runnable> mAfters;
|
|
||||||
private final List<Runnable> mAfterAlls;
|
|
||||||
|
|
||||||
private final int mIterations;
|
|
||||||
private final String mTestTag;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private List<TransitionResult> mResults = null;
|
|
||||||
|
|
||||||
private TransitionRunner(TransitionBuilder builder) {
|
|
||||||
mScreenRecorder = builder.mScreenRecorder;
|
|
||||||
mWmTraceMonitor = builder.mWmTraceMonitor;
|
|
||||||
mLayersTraceMonitor = builder.mLayersTraceMonitor;
|
|
||||||
mFrameStatsMonitor = builder.mFrameStatsMonitor;
|
|
||||||
|
|
||||||
mAllRunsMonitors = builder.mAllRunsMonitors;
|
|
||||||
mPerRunMonitors = builder.mPerRunMonitors;
|
|
||||||
mBeforeAlls = builder.mBeforeAlls;
|
|
||||||
mBefores = builder.mBefores;
|
|
||||||
mTransitions = builder.mTransitions;
|
|
||||||
mAfters = builder.mAfters;
|
|
||||||
mAfterAlls = builder.mAfterAlls;
|
|
||||||
|
|
||||||
mIterations = builder.mIterations;
|
|
||||||
mTestTag = builder.mTestTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TransitionBuilder newBuilder() {
|
|
||||||
return newBuilder(OUTPUT_DIR.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TransitionBuilder newBuilder(String outputDir) {
|
|
||||||
return new TransitionBuilder(outputDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs the composed transition and calls monitors at the appropriate stages. If jank monitor
|
|
||||||
* is enabled, transitions with jank are skipped.
|
|
||||||
*
|
|
||||||
* @return itself
|
|
||||||
*/
|
|
||||||
public TransitionRunner run() {
|
|
||||||
mResults = new ArrayList<>();
|
|
||||||
mAllRunsMonitors.forEach(ITransitionMonitor::start);
|
|
||||||
mBeforeAlls.forEach(Runnable::run);
|
|
||||||
for (int iteration = 0; iteration < mIterations; iteration++) {
|
|
||||||
mBefores.forEach(Runnable::run);
|
|
||||||
mPerRunMonitors.forEach(ITransitionMonitor::start);
|
|
||||||
mTransitions.forEach(Runnable::run);
|
|
||||||
mPerRunMonitors.forEach(ITransitionMonitor::stop);
|
|
||||||
mAfters.forEach(Runnable::run);
|
|
||||||
if (runJankFree() && mFrameStatsMonitor.jankyFramesDetected()) {
|
|
||||||
String msg = String.format("Skipping iteration %d/%d for test %s due to jank. %s",
|
|
||||||
iteration, mIterations - 1, mTestTag, mFrameStatsMonitor.toString());
|
|
||||||
Log.e(TAG, msg);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
mResults.add(saveResult(iteration));
|
|
||||||
}
|
|
||||||
mAfterAlls.forEach(Runnable::run);
|
|
||||||
mAllRunsMonitors.forEach(monitor -> {
|
|
||||||
monitor.stop();
|
|
||||||
monitor.save(mTestTag);
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of transition results.
|
|
||||||
*
|
|
||||||
* @return list of transition results.
|
|
||||||
*/
|
|
||||||
public List<TransitionResult> getResults() {
|
|
||||||
if (mResults == null) {
|
|
||||||
throw new IllegalStateException("Results do not exist!");
|
|
||||||
}
|
|
||||||
return mResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all transition results that are not marked for saving.
|
|
||||||
*
|
|
||||||
* @return list of transition results.
|
|
||||||
*/
|
|
||||||
public void deleteResults() {
|
|
||||||
if (mResults == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mResults.stream()
|
|
||||||
.filter(TransitionResult::canDelete)
|
|
||||||
.forEach(TransitionResult::delete);
|
|
||||||
mResults = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves monitor results to file.
|
|
||||||
*
|
|
||||||
* @return object containing paths to test artifacts
|
|
||||||
*/
|
|
||||||
private TransitionResult saveResult(int iteration) {
|
|
||||||
Path windowTrace = null;
|
|
||||||
Path layerTrace = null;
|
|
||||||
Path screenCaptureVideo = null;
|
|
||||||
|
|
||||||
if (mPerRunMonitors.contains(mWmTraceMonitor)) {
|
|
||||||
windowTrace = mWmTraceMonitor.save(mTestTag, iteration);
|
|
||||||
}
|
|
||||||
if (mPerRunMonitors.contains(mLayersTraceMonitor)) {
|
|
||||||
layerTrace = mLayersTraceMonitor.save(mTestTag, iteration);
|
|
||||||
}
|
|
||||||
if (mPerRunMonitors.contains(mScreenRecorder)) {
|
|
||||||
screenCaptureVideo = mScreenRecorder.save(mTestTag, iteration);
|
|
||||||
}
|
|
||||||
return new TransitionResult(layerTrace, windowTrace, screenCaptureVideo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean runJankFree() {
|
|
||||||
return mPerRunMonitors.contains(mFrameStatsMonitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTestTag() {
|
|
||||||
return mTestTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores paths to all test artifacts.
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
public static class TransitionResult {
|
|
||||||
@Nullable
|
|
||||||
public final Path layersTrace;
|
|
||||||
@Nullable
|
|
||||||
public final Path windowManagerTrace;
|
|
||||||
@Nullable
|
|
||||||
public final Path screenCaptureVideo;
|
|
||||||
private boolean flaggedForSaving;
|
|
||||||
|
|
||||||
public TransitionResult(@Nullable Path layersTrace, @Nullable Path windowManagerTrace,
|
|
||||||
@Nullable Path screenCaptureVideo) {
|
|
||||||
this.layersTrace = layersTrace;
|
|
||||||
this.windowManagerTrace = windowManagerTrace;
|
|
||||||
this.screenCaptureVideo = screenCaptureVideo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void flagForSaving() {
|
|
||||||
flaggedForSaving = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean canDelete() {
|
|
||||||
return !flaggedForSaving;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean layersTraceExists() {
|
|
||||||
return layersTrace != null && layersTrace.toFile().exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getLayersTrace() {
|
|
||||||
try {
|
|
||||||
return Files.toByteArray(this.layersTrace.toFile());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path getLayersTracePath() {
|
|
||||||
return layersTrace;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean windowManagerTraceExists() {
|
|
||||||
return windowManagerTrace != null && windowManagerTrace.toFile().exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getWindowManagerTrace() {
|
|
||||||
try {
|
|
||||||
return Files.toByteArray(this.windowManagerTrace.toFile());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path getWindowManagerTracePath() {
|
|
||||||
return windowManagerTrace;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean screenCaptureVideoExists() {
|
|
||||||
return screenCaptureVideo != null && screenCaptureVideo.toFile().exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path screenCaptureVideoPath() {
|
|
||||||
return screenCaptureVideo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete() {
|
|
||||||
if (layersTraceExists()) layersTrace.toFile().delete();
|
|
||||||
if (windowManagerTraceExists()) windowManagerTrace.toFile().delete();
|
|
||||||
if (screenCaptureVideoExists()) screenCaptureVideo.toFile().delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a {@link TransitionRunner} instance.
|
|
||||||
*/
|
|
||||||
public static class TransitionBuilder {
|
|
||||||
private ScreenRecorder mScreenRecorder;
|
|
||||||
private WindowManagerTraceMonitor mWmTraceMonitor;
|
|
||||||
private LayersTraceMonitor mLayersTraceMonitor;
|
|
||||||
private WindowAnimationFrameStatsMonitor mFrameStatsMonitor;
|
|
||||||
|
|
||||||
private List<ITransitionMonitor> mAllRunsMonitors = new LinkedList<>();
|
|
||||||
private List<ITransitionMonitor> mPerRunMonitors = new LinkedList<>();
|
|
||||||
private List<Runnable> mBeforeAlls = new LinkedList<>();
|
|
||||||
private List<Runnable> mBefores = new LinkedList<>();
|
|
||||||
private List<Runnable> mTransitions = new LinkedList<>();
|
|
||||||
private List<Runnable> mAfters = new LinkedList<>();
|
|
||||||
private List<Runnable> mAfterAlls = new LinkedList<>();
|
|
||||||
|
|
||||||
private boolean mRunJankFree = true;
|
|
||||||
private boolean mCaptureWindowManagerTrace = true;
|
|
||||||
private boolean mCaptureLayersTrace = true;
|
|
||||||
private boolean mRecordEachRun = false;
|
|
||||||
private int mIterations = 1;
|
|
||||||
private String mTestTag = "";
|
|
||||||
|
|
||||||
private boolean mRecordAllRuns = false;
|
|
||||||
|
|
||||||
public TransitionBuilder(String outputDir) {
|
|
||||||
mScreenRecorder = new ScreenRecorder();
|
|
||||||
mWmTraceMonitor = new WindowManagerTraceMonitor(outputDir);
|
|
||||||
mLayersTraceMonitor = new LayersTraceMonitor(outputDir);
|
|
||||||
mFrameStatsMonitor = new
|
|
||||||
WindowAnimationFrameStatsMonitor(InstrumentationRegistry.getInstrumentation());
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionRunner build() {
|
|
||||||
if (mCaptureWindowManagerTrace) {
|
|
||||||
mPerRunMonitors.add(mWmTraceMonitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mCaptureLayersTrace) {
|
|
||||||
mPerRunMonitors.add(mLayersTraceMonitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mRunJankFree) {
|
|
||||||
mPerRunMonitors.add(mFrameStatsMonitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mRecordAllRuns) {
|
|
||||||
mAllRunsMonitors.add(mScreenRecorder);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mRecordEachRun) {
|
|
||||||
mPerRunMonitors.add(mScreenRecorder);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new TransitionRunner(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionBuilder runBeforeAll(Runnable runnable) {
|
|
||||||
mBeforeAlls.add(runnable);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionBuilder runBefore(Runnable runnable) {
|
|
||||||
mBefores.add(runnable);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionBuilder run(Runnable runnable) {
|
|
||||||
mTransitions.add(runnable);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionBuilder runAfter(Runnable runnable) {
|
|
||||||
mAfters.add(runnable);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionBuilder runAfterAll(Runnable runnable) {
|
|
||||||
mAfterAlls.add(runnable);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionBuilder repeat(int iterations) {
|
|
||||||
mIterations = iterations;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionBuilder skipWindowManagerTrace() {
|
|
||||||
mCaptureWindowManagerTrace = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionBuilder skipLayersTrace() {
|
|
||||||
mCaptureLayersTrace = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionBuilder includeJankyRuns() {
|
|
||||||
mRunJankFree = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionBuilder recordEachRun() {
|
|
||||||
if (mRecordAllRuns) {
|
|
||||||
throw new IllegalArgumentException("Invalid option with recordAllRuns");
|
|
||||||
}
|
|
||||||
mRecordEachRun = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionBuilder recordAllRuns() {
|
|
||||||
if (mRecordEachRun) {
|
|
||||||
throw new IllegalArgumentException("Invalid option with recordEachRun");
|
|
||||||
}
|
|
||||||
mRecordAllRuns = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransitionBuilder withTag(String testTag) {
|
|
||||||
if (testTag.contains(" ")) {
|
|
||||||
throw new IllegalArgumentException("The test tag can not contain spaces since it "
|
|
||||||
+ "is a part of the file name");
|
|
||||||
}
|
|
||||||
mTestTag = testTag;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,240 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.android.server.wm.flicker.Assertions.Result;
|
|
||||||
import com.android.server.wm.nano.AppWindowTokenProto;
|
|
||||||
import com.android.server.wm.nano.StackProto;
|
|
||||||
import com.android.server.wm.nano.TaskProto;
|
|
||||||
import com.android.server.wm.nano.WindowManagerTraceFileProto;
|
|
||||||
import com.android.server.wm.nano.WindowManagerTraceProto;
|
|
||||||
import com.android.server.wm.nano.WindowStateProto;
|
|
||||||
import com.android.server.wm.nano.WindowTokenProto;
|
|
||||||
|
|
||||||
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains a collection of parsed WindowManager trace entries and assertions to apply over
|
|
||||||
* a single entry.
|
|
||||||
*
|
|
||||||
* Each entry is parsed into a list of {@link WindowManagerTrace.Entry} objects.
|
|
||||||
*/
|
|
||||||
public class WindowManagerTrace {
|
|
||||||
private static final int DEFAULT_DISPLAY = 0;
|
|
||||||
private final List<Entry> mEntries;
|
|
||||||
@Nullable
|
|
||||||
final private Path mSource;
|
|
||||||
|
|
||||||
private WindowManagerTrace(List<Entry> entries, Path source) {
|
|
||||||
this.mEntries = entries;
|
|
||||||
this.mSource = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses {@code WindowManagerTraceFileProto} from {@code data} and uses the proto to
|
|
||||||
* generates a list of trace entries.
|
|
||||||
*
|
|
||||||
* @param data binary proto data
|
|
||||||
* @param source Path to source of data for additional debug information
|
|
||||||
*/
|
|
||||||
public static WindowManagerTrace parseFrom(byte[] data, Path source) {
|
|
||||||
List<Entry> entries = new ArrayList<>();
|
|
||||||
|
|
||||||
WindowManagerTraceFileProto fileProto;
|
|
||||||
try {
|
|
||||||
fileProto = WindowManagerTraceFileProto.parseFrom(data);
|
|
||||||
} catch (InvalidProtocolBufferNanoException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
for (WindowManagerTraceProto entryProto : fileProto.entry) {
|
|
||||||
entries.add(new Entry(entryProto));
|
|
||||||
}
|
|
||||||
return new WindowManagerTrace(entries, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WindowManagerTrace parseFrom(byte[] data) {
|
|
||||||
return parseFrom(data, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Entry> getEntries() {
|
|
||||||
return mEntries;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entry getEntry(long timestamp) {
|
|
||||||
Optional<Entry> entry = mEntries.stream()
|
|
||||||
.filter(e -> e.getTimestamp() == timestamp)
|
|
||||||
.findFirst();
|
|
||||||
if (!entry.isPresent()) {
|
|
||||||
throw new RuntimeException("Entry does not exist for timestamp " + timestamp);
|
|
||||||
}
|
|
||||||
return entry.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<Path> getSource() {
|
|
||||||
return Optional.ofNullable(mSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a single WindowManager trace entry.
|
|
||||||
*/
|
|
||||||
public static class Entry implements ITraceEntry {
|
|
||||||
private final WindowManagerTraceProto mProto;
|
|
||||||
|
|
||||||
public Entry(WindowManagerTraceProto proto) {
|
|
||||||
mProto = proto;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Result isWindowVisible(String windowTitle,
|
|
||||||
WindowTokenProto[] windowTokenProtos) {
|
|
||||||
boolean titleFound = false;
|
|
||||||
for (WindowTokenProto windowToken : windowTokenProtos) {
|
|
||||||
for (WindowStateProto windowState : windowToken.windows) {
|
|
||||||
if (windowState.identifier.title.contains(windowTitle)) {
|
|
||||||
titleFound = true;
|
|
||||||
if (isVisible(windowState)) {
|
|
||||||
return new Result(true /* success */,
|
|
||||||
windowState.identifier.title + " is visible");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String reason;
|
|
||||||
if (!titleFound) {
|
|
||||||
reason = windowTitle + " cannot be found";
|
|
||||||
} else {
|
|
||||||
reason = windowTitle + " is invisible";
|
|
||||||
}
|
|
||||||
return new Result(false /* success */, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isVisible(WindowStateProto windowState) {
|
|
||||||
return windowState.windowContainer.visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getTimestamp() {
|
|
||||||
return mProto.elapsedRealtimeNanos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns window title of the top most visible app window.
|
|
||||||
*/
|
|
||||||
private String getTopVisibleAppWindow() {
|
|
||||||
StackProto[] stacks = mProto.windowManagerService.rootWindowContainer
|
|
||||||
.displays[DEFAULT_DISPLAY].stacks;
|
|
||||||
for (StackProto stack : stacks) {
|
|
||||||
for (TaskProto task : stack.tasks) {
|
|
||||||
for (AppWindowTokenProto token : task.appWindowTokens) {
|
|
||||||
for (WindowStateProto windowState : token.windowToken.windows) {
|
|
||||||
if (windowState.windowContainer.visible) {
|
|
||||||
return task.appWindowTokens[0].name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if aboveAppWindow with {@code windowTitle} is visible.
|
|
||||||
*/
|
|
||||||
public Result isAboveAppWindowVisible(String windowTitle) {
|
|
||||||
WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
|
|
||||||
.rootWindowContainer
|
|
||||||
.displays[DEFAULT_DISPLAY].aboveAppWindows;
|
|
||||||
Result result = isWindowVisible(windowTitle, windowTokenProtos);
|
|
||||||
return new Result(result.success, getTimestamp(), "showsAboveAppWindow", result.reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if belowAppWindow with {@code windowTitle} is visible.
|
|
||||||
*/
|
|
||||||
public Result isBelowAppWindowVisible(String windowTitle) {
|
|
||||||
WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
|
|
||||||
.rootWindowContainer
|
|
||||||
.displays[DEFAULT_DISPLAY].belowAppWindows;
|
|
||||||
Result result = isWindowVisible(windowTitle, windowTokenProtos);
|
|
||||||
return new Result(result.success, getTimestamp(), "isBelowAppWindowVisible",
|
|
||||||
result.reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if imeWindow with {@code windowTitle} is visible.
|
|
||||||
*/
|
|
||||||
public Result isImeWindowVisible(String windowTitle) {
|
|
||||||
WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
|
|
||||||
.rootWindowContainer
|
|
||||||
.displays[DEFAULT_DISPLAY].imeWindows;
|
|
||||||
Result result = isWindowVisible(windowTitle, windowTokenProtos);
|
|
||||||
return new Result(result.success, getTimestamp(), "isImeWindowVisible",
|
|
||||||
result.reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if app window with {@code windowTitle} is on top.
|
|
||||||
*/
|
|
||||||
public Result isVisibleAppWindowOnTop(String windowTitle) {
|
|
||||||
String topAppWindow = getTopVisibleAppWindow();
|
|
||||||
boolean success = topAppWindow.contains(windowTitle);
|
|
||||||
String reason = "wanted=" + windowTitle + " found=" + topAppWindow;
|
|
||||||
return new Result(success, getTimestamp(), "isAppWindowOnTop", reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if app window with {@code windowTitle} is visible.
|
|
||||||
*/
|
|
||||||
public Result isAppWindowVisible(String windowTitle) {
|
|
||||||
final String assertionName = "isAppWindowVisible";
|
|
||||||
boolean titleFound = false;
|
|
||||||
StackProto[] stacks = mProto.windowManagerService.rootWindowContainer
|
|
||||||
.displays[DEFAULT_DISPLAY].stacks;
|
|
||||||
for (StackProto stack : stacks) {
|
|
||||||
for (TaskProto task : stack.tasks) {
|
|
||||||
for (AppWindowTokenProto token : task.appWindowTokens) {
|
|
||||||
if (token.name.contains(windowTitle)) {
|
|
||||||
titleFound = true;
|
|
||||||
for (WindowStateProto windowState : token.windowToken.windows) {
|
|
||||||
if (windowState.windowContainer.visible) {
|
|
||||||
return new Result(true /* success */, getTimestamp(),
|
|
||||||
assertionName, "Window " + token.name +
|
|
||||||
"is visible");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String reason;
|
|
||||||
if (!titleFound) {
|
|
||||||
reason = "Window " + windowTitle + " cannot be found";
|
|
||||||
} else {
|
|
||||||
reason = "Window " + windowTitle + " is invisible";
|
|
||||||
}
|
|
||||||
return new Result(false /* success */, getTimestamp(), assertionName, reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.view.Surface;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper functions to retrieve system window sizes and positions.
|
|
||||||
*/
|
|
||||||
public class WindowUtils {
|
|
||||||
|
|
||||||
public static Rect getDisplayBounds() {
|
|
||||||
Point display = new Point();
|
|
||||||
WindowManager wm =
|
|
||||||
(WindowManager) InstrumentationRegistry.getContext().getSystemService(
|
|
||||||
Context.WINDOW_SERVICE);
|
|
||||||
wm.getDefaultDisplay().getRealSize(display);
|
|
||||||
return new Rect(0, 0, display.x, display.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getCurrentRotation() {
|
|
||||||
WindowManager wm =
|
|
||||||
(WindowManager) InstrumentationRegistry.getContext().getSystemService(
|
|
||||||
Context.WINDOW_SERVICE);
|
|
||||||
return wm.getDefaultDisplay().getRotation();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Rect getDisplayBounds(int requestedRotation) {
|
|
||||||
Rect displayBounds = getDisplayBounds();
|
|
||||||
int currentDisplayRotation = getCurrentRotation();
|
|
||||||
|
|
||||||
boolean displayIsRotated = (currentDisplayRotation == Surface.ROTATION_90 ||
|
|
||||||
currentDisplayRotation == Surface.ROTATION_270);
|
|
||||||
|
|
||||||
boolean requestedDisplayIsRotated = requestedRotation == Surface.ROTATION_90 ||
|
|
||||||
requestedRotation == Surface.ROTATION_270;
|
|
||||||
|
|
||||||
// if the current orientation changes with the requested rotation,
|
|
||||||
// flip height and width of display bounds.
|
|
||||||
if (displayIsRotated != requestedDisplayIsRotated) {
|
|
||||||
return new Rect(0, 0, displayBounds.height(), displayBounds.width());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Rect(0, 0, displayBounds.width(), displayBounds.height());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static Rect getAppPosition(int requestedRotation) {
|
|
||||||
Rect displayBounds = getDisplayBounds();
|
|
||||||
int currentDisplayRotation = getCurrentRotation();
|
|
||||||
|
|
||||||
boolean displayIsRotated = currentDisplayRotation == Surface.ROTATION_90 ||
|
|
||||||
currentDisplayRotation == Surface.ROTATION_270;
|
|
||||||
|
|
||||||
boolean requestedAppIsRotated = requestedRotation == Surface.ROTATION_90 ||
|
|
||||||
requestedRotation == Surface.ROTATION_270;
|
|
||||||
|
|
||||||
// display size will change if the display is reflected. Flip height and width of app if the
|
|
||||||
// requested rotation is different from the current rotation.
|
|
||||||
if (displayIsRotated != requestedAppIsRotated) {
|
|
||||||
return new Rect(0, 0, displayBounds.height(), displayBounds.width());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Rect(0, 0, displayBounds.width(), displayBounds.height());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Rect getStatusBarPosition(int requestedRotation) {
|
|
||||||
Resources resources = InstrumentationRegistry.getContext().getResources();
|
|
||||||
String resourceName;
|
|
||||||
Rect displayBounds = getDisplayBounds();
|
|
||||||
int width;
|
|
||||||
if (requestedRotation == Surface.ROTATION_0 || requestedRotation == Surface.ROTATION_180) {
|
|
||||||
resourceName = "status_bar_height_portrait";
|
|
||||||
width = Math.min(displayBounds.width(), displayBounds.height());
|
|
||||||
} else {
|
|
||||||
resourceName = "status_bar_height_landscape";
|
|
||||||
width = Math.max(displayBounds.width(), displayBounds.height());
|
|
||||||
}
|
|
||||||
|
|
||||||
int resourceId = resources.getIdentifier(resourceName, "dimen", "android");
|
|
||||||
int height = resources.getDimensionPixelSize(resourceId);
|
|
||||||
|
|
||||||
return new Rect(0, 0, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Rect getNavigationBarPosition(int requestedRotation) {
|
|
||||||
Resources resources = InstrumentationRegistry.getContext().getResources();
|
|
||||||
Rect displayBounds = getDisplayBounds();
|
|
||||||
int displayWidth = Math.min(displayBounds.width(), displayBounds.height());
|
|
||||||
int displayHeight = Math.max(displayBounds.width(), displayBounds.height());
|
|
||||||
int resourceId;
|
|
||||||
if (requestedRotation == Surface.ROTATION_0 || requestedRotation == Surface.ROTATION_180) {
|
|
||||||
resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
|
|
||||||
int height = resources.getDimensionPixelSize(resourceId);
|
|
||||||
return new Rect(0, displayHeight - height, displayWidth, displayHeight);
|
|
||||||
} else {
|
|
||||||
resourceId = resources.getIdentifier("navigation_bar_width", "dimen", "android");
|
|
||||||
int width = resources.getDimensionPixelSize(resourceId);
|
|
||||||
// swap display dimensions in landscape or seascape mode
|
|
||||||
int temp = displayHeight;
|
|
||||||
displayHeight = displayWidth;
|
|
||||||
displayWidth = temp;
|
|
||||||
if (requestedRotation == Surface.ROTATION_90) {
|
|
||||||
return new Rect(0, 0, width, displayHeight);
|
|
||||||
} else {
|
|
||||||
return new Rect(displayWidth - width, 0, displayWidth, displayHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getNavigationBarHeight() {
|
|
||||||
Resources resources = InstrumentationRegistry.getContext().getResources();
|
|
||||||
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
|
|
||||||
return resources.getDimensionPixelSize(resourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getDockedStackDividerInset() {
|
|
||||||
Resources resources = InstrumentationRegistry.getContext().getResources();
|
|
||||||
int resourceId = resources.getIdentifier("docked_stack_divider_insets", "dimen",
|
|
||||||
"android");
|
|
||||||
return resources.getDimensionPixelSize(resourceId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,192 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertAbout;
|
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.android.server.wm.flicker.Assertions.Result;
|
|
||||||
import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
|
|
||||||
|
|
||||||
import com.google.common.truth.FailureMetadata;
|
|
||||||
import com.google.common.truth.Subject;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Truth subject for {@link WindowManagerTrace} objects.
|
|
||||||
*/
|
|
||||||
public class WmTraceSubject extends Subject<WmTraceSubject, WindowManagerTrace> {
|
|
||||||
// Boiler-plate Subject.Factory for WmTraceSubject
|
|
||||||
private static final Subject.Factory<WmTraceSubject, WindowManagerTrace> FACTORY =
|
|
||||||
new Subject.Factory<WmTraceSubject, WindowManagerTrace>() {
|
|
||||||
@Override
|
|
||||||
public WmTraceSubject createSubject(
|
|
||||||
FailureMetadata fm, @Nullable WindowManagerTrace target) {
|
|
||||||
return new WmTraceSubject(fm, target);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private AssertionsChecker<WindowManagerTrace.Entry> mChecker = new AssertionsChecker<>();
|
|
||||||
|
|
||||||
private WmTraceSubject(FailureMetadata fm, @Nullable WindowManagerTrace subject) {
|
|
||||||
super(fm, subject);
|
|
||||||
}
|
|
||||||
|
|
||||||
// User-defined entry point
|
|
||||||
public static WmTraceSubject assertThat(@Nullable WindowManagerTrace entry) {
|
|
||||||
return assertAbout(FACTORY).that(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
// User-defined entry point
|
|
||||||
public static WmTraceSubject assertThat(@Nullable TransitionResult result) {
|
|
||||||
WindowManagerTrace entries = WindowManagerTrace.parseFrom(result.getWindowManagerTrace(),
|
|
||||||
result.getWindowManagerTracePath());
|
|
||||||
return assertWithMessage(result.toString()).about(FACTORY).that(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Static method for getting the subject factory (for use with assertAbout())
|
|
||||||
public static Subject.Factory<WmTraceSubject, WindowManagerTrace> entries() {
|
|
||||||
return FACTORY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void forAllEntries() {
|
|
||||||
test();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void forRange(long startTime, long endTime) {
|
|
||||||
mChecker.filterByRange(startTime, endTime);
|
|
||||||
test();
|
|
||||||
}
|
|
||||||
|
|
||||||
public WmTraceSubject then() {
|
|
||||||
mChecker.checkChangingAssertions();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void inTheBeginning() {
|
|
||||||
if (getSubject().getEntries().isEmpty()) {
|
|
||||||
fail("No entries found.");
|
|
||||||
}
|
|
||||||
mChecker.checkFirstEntry();
|
|
||||||
test();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void atTheEnd() {
|
|
||||||
if (getSubject().getEntries().isEmpty()) {
|
|
||||||
fail("No entries found.");
|
|
||||||
}
|
|
||||||
mChecker.checkLastEntry();
|
|
||||||
test();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void test() {
|
|
||||||
List<Result> failures = mChecker.test(getSubject().getEntries());
|
|
||||||
if (!failures.isEmpty()) {
|
|
||||||
Optional<Path> failureTracePath = getSubject().getSource();
|
|
||||||
String failureLogs = failures.stream().map(Result::toString)
|
|
||||||
.collect(Collectors.joining("\n"));
|
|
||||||
String tracePath = "";
|
|
||||||
if (failureTracePath.isPresent()) {
|
|
||||||
tracePath = "\nWindowManager Trace can be found in: "
|
|
||||||
+ failureTracePath.get().toAbsolutePath() + "\n";
|
|
||||||
}
|
|
||||||
fail(tracePath + failureLogs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public WmTraceSubject showsAboveAppWindow(String partialWindowTitle) {
|
|
||||||
mChecker.add(entry -> entry.isAboveAppWindowVisible(partialWindowTitle),
|
|
||||||
"showsAboveAppWindow(" + partialWindowTitle + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WmTraceSubject hidesAboveAppWindow(String partialWindowTitle) {
|
|
||||||
mChecker.add(entry -> entry.isAboveAppWindowVisible(partialWindowTitle).negate(),
|
|
||||||
"hidesAboveAppWindow" + "(" + partialWindowTitle + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WmTraceSubject showsBelowAppWindow(String partialWindowTitle) {
|
|
||||||
mChecker.add(entry -> entry.isBelowAppWindowVisible(partialWindowTitle),
|
|
||||||
"showsBelowAppWindow(" + partialWindowTitle + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WmTraceSubject hidesBelowAppWindow(String partialWindowTitle) {
|
|
||||||
mChecker.add(entry -> entry.isBelowAppWindowVisible(partialWindowTitle).negate(),
|
|
||||||
"hidesBelowAppWindow" + "(" + partialWindowTitle + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WmTraceSubject showsImeWindow(String partialWindowTitle) {
|
|
||||||
mChecker.add(entry -> entry.isImeWindowVisible(partialWindowTitle),
|
|
||||||
"showsBelowAppWindow(" + partialWindowTitle + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WmTraceSubject hidesImeWindow(String partialWindowTitle) {
|
|
||||||
mChecker.add(entry -> entry.isImeWindowVisible(partialWindowTitle).negate(),
|
|
||||||
"hidesImeWindow" + "(" + partialWindowTitle + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WmTraceSubject showsAppWindowOnTop(String partialWindowTitle) {
|
|
||||||
mChecker.add(
|
|
||||||
entry -> {
|
|
||||||
Result result = entry.isAppWindowVisible(partialWindowTitle);
|
|
||||||
if (result.passed()) {
|
|
||||||
result = entry.isVisibleAppWindowOnTop(partialWindowTitle);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
"showsAppWindowOnTop(" + partialWindowTitle + ")"
|
|
||||||
);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WmTraceSubject hidesAppWindowOnTop(String partialWindowTitle) {
|
|
||||||
mChecker.add(
|
|
||||||
entry -> {
|
|
||||||
Result result = entry.isAppWindowVisible(partialWindowTitle).negate();
|
|
||||||
if (result.failed()) {
|
|
||||||
result = entry.isVisibleAppWindowOnTop(partialWindowTitle).negate();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
"hidesAppWindowOnTop(" + partialWindowTitle + ")"
|
|
||||||
);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WmTraceSubject showsAppWindow(String partialWindowTitle) {
|
|
||||||
mChecker.add(entry -> entry.isAppWindowVisible(partialWindowTitle),
|
|
||||||
"showsAppWindow(" + partialWindowTitle + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WmTraceSubject hidesAppWindow(String partialWindowTitle) {
|
|
||||||
mChecker.add(entry -> entry.isAppWindowVisible(partialWindowTitle).negate(),
|
|
||||||
"hidesAppWindow(" + partialWindowTitle + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,263 +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.server.wm.flicker.helpers;
|
|
||||||
|
|
||||||
import static android.os.SystemClock.sleep;
|
|
||||||
import static android.system.helpers.OverviewHelper.isRecentsInLauncher;
|
|
||||||
import static android.view.Surface.ROTATION_0;
|
|
||||||
|
|
||||||
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.support.test.launcherhelper.LauncherStrategyFactory;
|
|
||||||
import android.support.test.uiautomator.By;
|
|
||||||
import android.support.test.uiautomator.BySelector;
|
|
||||||
import android.support.test.uiautomator.Configurator;
|
|
||||||
import android.support.test.uiautomator.UiDevice;
|
|
||||||
import android.support.test.uiautomator.UiObject2;
|
|
||||||
import android.support.test.uiautomator.Until;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.Rational;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewConfiguration;
|
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
|
||||||
|
|
||||||
import com.android.server.wm.flicker.WindowUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collection of UI Automation helper functions.
|
|
||||||
*/
|
|
||||||
public class AutomationUtils {
|
|
||||||
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
|
|
||||||
private static final long FIND_TIMEOUT = 10000;
|
|
||||||
private static final long LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() * 2L;
|
|
||||||
private static final String TAG = "FLICKER";
|
|
||||||
|
|
||||||
public static void wakeUpAndGoToHomeScreen() {
|
|
||||||
UiDevice device = UiDevice.getInstance(InstrumentationRegistry
|
|
||||||
.getInstrumentation());
|
|
||||||
try {
|
|
||||||
device.wakeUp();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
device.pressHome();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets {@link android.app.UiAutomation#waitForIdle(long, long)} global timeout to 0 causing
|
|
||||||
* the {@link android.app.UiAutomation#waitForIdle(long, long)} function to timeout instantly.
|
|
||||||
* This removes some delays when using the UIAutomator library required to create fast UI
|
|
||||||
* transitions.
|
|
||||||
*/
|
|
||||||
public static void setFastWait() {
|
|
||||||
Configurator.getInstance().setWaitForIdleTimeout(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverts {@link android.app.UiAutomation#waitForIdle(long, long)} to default behavior.
|
|
||||||
*/
|
|
||||||
public static void setDefaultWait() {
|
|
||||||
Configurator.getInstance().setWaitForIdleTimeout(10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isQuickstepEnabled(UiDevice device) {
|
|
||||||
return device.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")) == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void openQuickstep(UiDevice device) {
|
|
||||||
if (isQuickstepEnabled(device)) {
|
|
||||||
int height = device.getDisplayHeight();
|
|
||||||
UiObject2 navBar = device.findObject(By.res(SYSTEMUI_PACKAGE, "navigation_bar_frame"));
|
|
||||||
|
|
||||||
Rect navBarVisibleBounds;
|
|
||||||
|
|
||||||
// TODO(vishnun) investigate why this object cannot be found.
|
|
||||||
if (navBar != null) {
|
|
||||||
navBarVisibleBounds = navBar.getVisibleBounds();
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Could not find nav bar, infer location");
|
|
||||||
navBarVisibleBounds = WindowUtils.getNavigationBarPosition(ROTATION_0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swipe from nav bar to 2/3rd down the screen.
|
|
||||||
device.swipe(
|
|
||||||
navBarVisibleBounds.centerX(), navBarVisibleBounds.centerY(),
|
|
||||||
navBarVisibleBounds.centerX(), height * 2 / 3,
|
|
||||||
(navBarVisibleBounds.centerY() - height * 2 / 3) / 100); // 100 px/step
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
device.pressRecentApps();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BySelector RECENTS = By.res(SYSTEMUI_PACKAGE, "recents_view");
|
|
||||||
|
|
||||||
// use a long timeout to wait until recents populated
|
|
||||||
if (device.wait(
|
|
||||||
Until.findObject(isRecentsInLauncher()
|
|
||||||
? getLauncherOverviewSelector(device) : RECENTS),
|
|
||||||
10000) == null) {
|
|
||||||
fail("Recents didn't appear");
|
|
||||||
}
|
|
||||||
device.waitForIdle();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clearRecents(UiDevice device) {
|
|
||||||
if (isQuickstepEnabled(device)) {
|
|
||||||
openQuickstep(device);
|
|
||||||
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
device.swipe(device.getDisplayWidth() / 2,
|
|
||||||
device.getDisplayHeight() / 2, device.getDisplayWidth(),
|
|
||||||
device.getDisplayHeight() / 2,
|
|
||||||
5);
|
|
||||||
|
|
||||||
BySelector clearAllSelector = By.res("com.google.android.apps.nexuslauncher",
|
|
||||||
"clear_all_button");
|
|
||||||
UiObject2 clearAllButton = device.wait(Until.findObject(clearAllSelector), 100);
|
|
||||||
if (clearAllButton != null) {
|
|
||||||
clearAllButton.click();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BySelector getLauncherOverviewSelector(UiDevice device) {
|
|
||||||
return By.res(device.getLauncherPackageName(), "overview_panel");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void longPressRecents(UiDevice device) {
|
|
||||||
BySelector recentsSelector = By.res(SYSTEMUI_PACKAGE, "recent_apps");
|
|
||||||
UiObject2 recentsButton = device.wait(Until.findObject(recentsSelector), FIND_TIMEOUT);
|
|
||||||
assertNotNull("Unable to find recents button", recentsButton);
|
|
||||||
recentsButton.click(LONG_PRESS_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void launchSplitScreen(UiDevice device) {
|
|
||||||
String mLauncherPackage = LauncherStrategyFactory.getInstance(device)
|
|
||||||
.getLauncherStrategy().getSupportedLauncherPackage();
|
|
||||||
|
|
||||||
if (isQuickstepEnabled(device)) {
|
|
||||||
// Quickstep enabled
|
|
||||||
openQuickstep(device);
|
|
||||||
|
|
||||||
BySelector overviewIconSelector = By.res(mLauncherPackage, "icon")
|
|
||||||
.clazz(View.class);
|
|
||||||
UiObject2 overviewIcon = device.wait(Until.findObject(overviewIconSelector),
|
|
||||||
FIND_TIMEOUT);
|
|
||||||
assertNotNull("Unable to find app icon in Overview", overviewIcon);
|
|
||||||
overviewIcon.click();
|
|
||||||
|
|
||||||
BySelector splitscreenButtonSelector = By.text("Split screen");
|
|
||||||
UiObject2 splitscreenButton = device.wait(Until.findObject(splitscreenButtonSelector),
|
|
||||||
FIND_TIMEOUT);
|
|
||||||
assertNotNull("Unable to find Split screen button in Overview", splitscreenButton);
|
|
||||||
splitscreenButton.click();
|
|
||||||
} else {
|
|
||||||
// Classic long press recents
|
|
||||||
longPressRecents(device);
|
|
||||||
}
|
|
||||||
// Wait for animation to complete.
|
|
||||||
sleep(2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void exitSplitScreen(UiDevice device) {
|
|
||||||
if (isQuickstepEnabled(device)) {
|
|
||||||
// Quickstep enabled
|
|
||||||
BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle");
|
|
||||||
UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
|
|
||||||
assertNotNull("Unable to find Split screen divider", divider);
|
|
||||||
|
|
||||||
// Drag the split screen divider to the top of the screen
|
|
||||||
divider.drag(new Point(device.getDisplayWidth() / 2, 0), 400);
|
|
||||||
} else {
|
|
||||||
// Classic long press recents
|
|
||||||
longPressRecents(device);
|
|
||||||
}
|
|
||||||
// Wait for animation to complete.
|
|
||||||
sleep(2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void resizeSplitScreen(UiDevice device, Rational windowHeightRatio) {
|
|
||||||
BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle");
|
|
||||||
UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
|
|
||||||
assertNotNull("Unable to find Split screen divider", divider);
|
|
||||||
int destHeight =
|
|
||||||
(int) (WindowUtils.getDisplayBounds().height() * windowHeightRatio.floatValue());
|
|
||||||
// Drag the split screen divider to so that the ratio of top window height and bottom
|
|
||||||
// window height is windowHeightRatio
|
|
||||||
device.drag(divider.getVisibleBounds().centerX(), divider.getVisibleBounds().centerY(),
|
|
||||||
device.getDisplayWidth() / 2, destHeight, 10);
|
|
||||||
//divider.drag(new Point(device.getDisplayWidth() / 2, destHeight), 400)
|
|
||||||
divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
|
|
||||||
|
|
||||||
// Wait for animation to complete.
|
|
||||||
sleep(2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void closePipWindow(UiDevice device) {
|
|
||||||
UiObject2 pipWindow = device.findObject(
|
|
||||||
By.res(SYSTEMUI_PACKAGE, "background"));
|
|
||||||
pipWindow.click();
|
|
||||||
UiObject2 exitPipObject = device.findObject(
|
|
||||||
By.res(SYSTEMUI_PACKAGE, "dismiss"));
|
|
||||||
exitPipObject.click();
|
|
||||||
// Wait for animation to complete.
|
|
||||||
sleep(2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void expandPipWindow(UiDevice device) {
|
|
||||||
UiObject2 pipWindow = device.findObject(
|
|
||||||
By.res(SYSTEMUI_PACKAGE, "background"));
|
|
||||||
pipWindow.click();
|
|
||||||
pipWindow.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void stopPackage(Context context, String packageName) {
|
|
||||||
runShellCommand("am force-stop " + packageName);
|
|
||||||
int packageUid;
|
|
||||||
try {
|
|
||||||
packageUid = context.getPackageManager().getPackageUid(packageName, /* flags= */0);
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
while (targetPackageIsRunning(packageUid)) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(100);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
//ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean targetPackageIsRunning(int uid) {
|
|
||||||
final String result = runShellCommand(
|
|
||||||
String.format("cmd activity get-uid-state %d", uid));
|
|
||||||
return !result.contains("(NONEXISTENT)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +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.server.wm.flicker.monitor;
|
|
||||||
|
|
||||||
import android.os.Environment;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collects test artifacts during a UI transition.
|
|
||||||
*/
|
|
||||||
public interface ITransitionMonitor {
|
|
||||||
Path OUTPUT_DIR = Paths.get(Environment.getExternalStorageDirectory().toString(), "flicker");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts monitor.
|
|
||||||
*/
|
|
||||||
void start();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops monitor.
|
|
||||||
*/
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves any monitor artifacts to file adding {@code testTag} and {@code iteration}
|
|
||||||
* to the file name.
|
|
||||||
*
|
|
||||||
* @param testTag suffix added to artifact name
|
|
||||||
* @param iteration suffix added to artifact name
|
|
||||||
*
|
|
||||||
* @return Path to saved artifact
|
|
||||||
*/
|
|
||||||
default Path save(String testTag, int iteration) {
|
|
||||||
return save(testTag + "_" + iteration);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves any monitor artifacts to file adding {@code testTag} to the file name.
|
|
||||||
*
|
|
||||||
* @param testTag suffix added to artifact name
|
|
||||||
*
|
|
||||||
* @return Path to saved artifact
|
|
||||||
*/
|
|
||||||
default Path save(String testTag) {
|
|
||||||
throw new UnsupportedOperationException("Save not implemented for this monitor");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +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.server.wm.flicker.monitor;
|
|
||||||
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.view.IWindowManager;
|
|
||||||
import android.view.WindowManagerGlobal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures Layers trace from SurfaceFlinger.
|
|
||||||
*/
|
|
||||||
public class LayersTraceMonitor extends TraceMonitor {
|
|
||||||
private IWindowManager mWm = WindowManagerGlobal.getWindowManagerService();
|
|
||||||
|
|
||||||
public LayersTraceMonitor() {
|
|
||||||
this(OUTPUT_DIR.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public LayersTraceMonitor(String outputDir) {
|
|
||||||
super(outputDir, "layers_trace.pb");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
setEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() throws RemoteException {
|
|
||||||
try {
|
|
||||||
return mWm.isLayerTracing();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setEnabled(boolean isEnabled) {
|
|
||||||
try {
|
|
||||||
mWm.setLayerTracing(isEnabled);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +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.server.wm.flicker.monitor;
|
|
||||||
|
|
||||||
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
|
|
||||||
|
|
||||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures screen contents and saves it as a mp4 video file.
|
|
||||||
*/
|
|
||||||
public class ScreenRecorder implements ITransitionMonitor {
|
|
||||||
@VisibleForTesting
|
|
||||||
public static final Path DEFAULT_OUTPUT_PATH = OUTPUT_DIR.resolve("transition.mp4");
|
|
||||||
private static final String TAG = "FLICKER";
|
|
||||||
private Thread recorderThread;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public static Path getPath(String testTag) {
|
|
||||||
return OUTPUT_DIR.resolve(testTag + ".mp4");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
OUTPUT_DIR.toFile().mkdirs();
|
|
||||||
String command = "screenrecord " + DEFAULT_OUTPUT_PATH;
|
|
||||||
recorderThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
Runtime.getRuntime().exec(command);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Error executing " + command, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
recorderThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
runShellCommand("killall -s 2 screenrecord");
|
|
||||||
try {
|
|
||||||
recorderThread.join();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Path save(String testTag) {
|
|
||||||
try {
|
|
||||||
Path targetPath = Files.move(DEFAULT_OUTPUT_PATH, getPath(testTag),
|
|
||||||
REPLACE_EXISTING);
|
|
||||||
Log.i(TAG, "Video saved to " + targetPath.toString());
|
|
||||||
return targetPath;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +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.server.wm.flicker.monitor;
|
|
||||||
|
|
||||||
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
|
|
||||||
|
|
||||||
import android.os.RemoteException;
|
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for monitors containing common logic to read the trace
|
|
||||||
* as a byte array and save the trace to another location.
|
|
||||||
*/
|
|
||||||
public abstract class TraceMonitor implements ITransitionMonitor {
|
|
||||||
public static final String TAG = "FLICKER";
|
|
||||||
private static final String TRACE_DIR = "/data/misc/wmtrace/";
|
|
||||||
|
|
||||||
private Path mOutputDir;
|
|
||||||
public String mTraceFileName;
|
|
||||||
|
|
||||||
public abstract boolean isEnabled() throws RemoteException;
|
|
||||||
|
|
||||||
public TraceMonitor(String outputDir, String traceFileName) {
|
|
||||||
mOutputDir = Paths.get(outputDir);
|
|
||||||
mTraceFileName = traceFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves trace file to the external storage directory suffixing the name with the testtag
|
|
||||||
* and iteration.
|
|
||||||
*
|
|
||||||
* Moves the trace file from the default location via a shell command since the test app
|
|
||||||
* does not have security privileges to access /data/misc/wmtrace.
|
|
||||||
*
|
|
||||||
* @param testTag suffix added to trace name used to identify trace
|
|
||||||
*
|
|
||||||
* @return Path to saved trace file
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Path save(String testTag) {
|
|
||||||
OUTPUT_DIR.toFile().mkdirs();
|
|
||||||
Path traceFileCopy = getOutputTraceFilePath(testTag);
|
|
||||||
|
|
||||||
// Read the input stream fully.
|
|
||||||
String copyCommand = String.format(Locale.getDefault(), "mv %s%s %s", TRACE_DIR,
|
|
||||||
mTraceFileName, traceFileCopy.toString());
|
|
||||||
runShellCommand(copyCommand);
|
|
||||||
return traceFileCopy;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public Path getOutputTraceFilePath(String testTag) {
|
|
||||||
return mOutputDir.resolve(mTraceFileName + "_" + testTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +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.server.wm.flicker.monitor;
|
|
||||||
|
|
||||||
import static android.view.FrameStats.UNDEFINED_TIME_NANO;
|
|
||||||
|
|
||||||
import android.app.Instrumentation;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.FrameStats;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Monitors {@link android.view.WindowAnimationFrameStats} to detect janky frames.
|
|
||||||
*
|
|
||||||
* Adapted from {@link androidx.test.jank.internal.WindowAnimationFrameStatsMonitorImpl}
|
|
||||||
* using the same threshold to determine jank.
|
|
||||||
*/
|
|
||||||
public class WindowAnimationFrameStatsMonitor implements ITransitionMonitor {
|
|
||||||
|
|
||||||
private static final String TAG = "FLICKER";
|
|
||||||
// Maximum normalized error in frame duration before the frame is considered janky
|
|
||||||
private static final double MAX_ERROR = 0.5f;
|
|
||||||
// Maximum normalized frame duration before the frame is considered a pause
|
|
||||||
private static final double PAUSE_THRESHOLD = 15.0f;
|
|
||||||
private Instrumentation mInstrumentation;
|
|
||||||
private FrameStats stats;
|
|
||||||
private int numJankyFrames;
|
|
||||||
private long mLongestFrameNano = 0L;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a WindowAnimationFrameStatsMonitor instance.
|
|
||||||
*/
|
|
||||||
public WindowAnimationFrameStatsMonitor(Instrumentation instrumentation) {
|
|
||||||
mInstrumentation = instrumentation;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void analyze() {
|
|
||||||
int frameCount = stats.getFrameCount();
|
|
||||||
long refreshPeriodNano = stats.getRefreshPeriodNano();
|
|
||||||
|
|
||||||
// Skip first frame
|
|
||||||
for (int i = 2; i < frameCount; i++) {
|
|
||||||
// Handle frames that have not been presented.
|
|
||||||
if (stats.getFramePresentedTimeNano(i) == UNDEFINED_TIME_NANO) {
|
|
||||||
// The animation must not have completed. Warn and break out of the loop.
|
|
||||||
Log.w(TAG, "Skipping fenced frame.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
long frameDurationNano = stats.getFramePresentedTimeNano(i) -
|
|
||||||
stats.getFramePresentedTimeNano(i - 1);
|
|
||||||
double normalized = (double) frameDurationNano / refreshPeriodNano;
|
|
||||||
if (normalized < PAUSE_THRESHOLD) {
|
|
||||||
if (normalized > 1.0f + MAX_ERROR) {
|
|
||||||
numJankyFrames++;
|
|
||||||
}
|
|
||||||
mLongestFrameNano = Math.max(mLongestFrameNano, frameDurationNano);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
// Clear out any previous data
|
|
||||||
numJankyFrames = 0;
|
|
||||||
mLongestFrameNano = 0;
|
|
||||||
mInstrumentation.getUiAutomation().clearWindowAnimationFrameStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
stats = mInstrumentation.getUiAutomation().getWindowAnimationFrameStats();
|
|
||||||
analyze();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean jankyFramesDetected() {
|
|
||||||
return stats.getFrameCount() > 0 && numJankyFrames > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return stats.toString() +
|
|
||||||
" RefreshPeriodNano:" + stats.getRefreshPeriodNano() +
|
|
||||||
" NumJankyFrames:" + numJankyFrames +
|
|
||||||
" LongestFrameNano:" + mLongestFrameNano;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +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.server.wm.flicker.monitor;
|
|
||||||
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.view.IWindowManager;
|
|
||||||
import android.view.WindowManagerGlobal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures WindowManager trace from WindowManager.
|
|
||||||
*/
|
|
||||||
public class WindowManagerTraceMonitor extends TraceMonitor {
|
|
||||||
private IWindowManager mWm = WindowManagerGlobal.getWindowManagerService();
|
|
||||||
|
|
||||||
public WindowManagerTraceMonitor() {
|
|
||||||
this(OUTPUT_DIR.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public WindowManagerTraceMonitor(String outputDir) {
|
|
||||||
super(outputDir, "wm_trace.pb");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
try {
|
|
||||||
mWm.startWindowTrace();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
throw new RuntimeException("Could not start trace", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
try {
|
|
||||||
mWm.stopWindowTrace();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
throw new RuntimeException("Could not stop trace", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() throws RemoteException{
|
|
||||||
return mWm.isWindowTraceEnabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +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.
|
|
||||||
//
|
|
||||||
|
|
||||||
android_test {
|
|
||||||
name: "FlickerLibTest",
|
|
||||||
// sign this with platform cert, so this test is allowed to call private platform apis
|
|
||||||
certificate: "platform",
|
|
||||||
platform_apis: true,
|
|
||||||
test_suites: ["tests"],
|
|
||||||
srcs: ["src/**/*.java"],
|
|
||||||
libs: ["android.test.runner"],
|
|
||||||
static_libs: [
|
|
||||||
"androidx.test.rules",
|
|
||||||
"platform-test-annotations",
|
|
||||||
"truth-prebuilt",
|
|
||||||
"platformprotosnano",
|
|
||||||
"layersprotosnano",
|
|
||||||
"flickerlib",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="com.android.server.wm.flicker">
|
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
|
|
||||||
<!-- Read and write traces from external storage -->
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
<!-- Capture screen contents -->
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
|
|
||||||
<!-- Run layers trace -->
|
|
||||||
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
|
|
||||||
<application android:label="FlickerLibTest">
|
|
||||||
<uses-library android:name="android.test.runner"/>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
android:targetPackage="com.android.server.wm.flicker"
|
|
||||||
android:label="WindowManager Flicker Lib Test">
|
|
||||||
</instrumentation>
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
|
||||||
-->
|
|
||||||
<configuration description="Config for WindowManager Flicker Tests">
|
|
||||||
<target_preparer class="com.google.android.tradefed.targetprep.GoogleDeviceSetup">
|
|
||||||
<!-- keeps the screen on during tests -->
|
|
||||||
<option name="screen-always-on" value="on" />
|
|
||||||
<!-- prevents the phone from restarting -->
|
|
||||||
<option name="force-skip-system-props" value="true" />
|
|
||||||
</target_preparer>
|
|
||||||
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
|
|
||||||
<option name="cleanup-apks" value="true"/>
|
|
||||||
<option name="test-file-name" value="FlickerLibTest.apk"/>
|
|
||||||
</target_preparer>
|
|
||||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
|
|
||||||
<option name="package" value="com.android.server.wm.flicker"/>
|
|
||||||
<option name="hidden-api-checks" value="false" />
|
|
||||||
</test>
|
|
||||||
</configuration>
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,181 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import com.android.server.wm.flicker.Assertions.Result;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains {@link AssertionsChecker} tests.
|
|
||||||
* To run this test: {@code atest FlickerLibTest:AssertionsCheckerTest}
|
|
||||||
*/
|
|
||||||
public class AssertionsCheckerTest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of SimpleEntry objects with {@code data} and incremental timestamps starting
|
|
||||||
* at 0.
|
|
||||||
*/
|
|
||||||
private static List<SimpleEntry> getTestEntries(int... data) {
|
|
||||||
List<SimpleEntry> entries = new ArrayList<>();
|
|
||||||
for (int i = 0; i < data.length; i++) {
|
|
||||||
entries.add(new SimpleEntry(i, data[i]));
|
|
||||||
}
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canCheckAllEntries() {
|
|
||||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
|
||||||
checker.add(SimpleEntry::isData42, "isData42");
|
|
||||||
|
|
||||||
List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1));
|
|
||||||
|
|
||||||
assertThat(failures).hasSize(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canCheckFirstEntry() {
|
|
||||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
|
||||||
checker.checkFirstEntry();
|
|
||||||
checker.add(SimpleEntry::isData42, "isData42");
|
|
||||||
|
|
||||||
List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1));
|
|
||||||
|
|
||||||
assertThat(failures).hasSize(1);
|
|
||||||
assertThat(failures.get(0).timestamp).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canCheckLastEntry() {
|
|
||||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
|
||||||
checker.checkLastEntry();
|
|
||||||
checker.add(SimpleEntry::isData42, "isData42");
|
|
||||||
|
|
||||||
List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1));
|
|
||||||
|
|
||||||
assertThat(failures).hasSize(1);
|
|
||||||
assertThat(failures.get(0).timestamp).isEqualTo(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canCheckRangeOfEntries() {
|
|
||||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
|
||||||
checker.filterByRange(1, 2);
|
|
||||||
checker.add(SimpleEntry::isData42, "isData42");
|
|
||||||
|
|
||||||
List<Result> failures = checker.test(getTestEntries(1, 42, 42, 1, 1));
|
|
||||||
|
|
||||||
assertThat(failures).hasSize(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void emptyRangePasses() {
|
|
||||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
|
||||||
checker.filterByRange(9, 10);
|
|
||||||
checker.add(SimpleEntry::isData42, "isData42");
|
|
||||||
|
|
||||||
List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1));
|
|
||||||
|
|
||||||
assertThat(failures).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canCheckChangingAssertions() {
|
|
||||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
|
||||||
checker.add(SimpleEntry::isData42, "isData42");
|
|
||||||
checker.add(SimpleEntry::isData0, "isData0");
|
|
||||||
checker.checkChangingAssertions();
|
|
||||||
|
|
||||||
List<Result> failures = checker.test(getTestEntries(42, 0, 0, 0, 0));
|
|
||||||
|
|
||||||
assertThat(failures).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canCheckChangingAssertions_withNoAssertions() {
|
|
||||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
|
||||||
checker.checkChangingAssertions();
|
|
||||||
|
|
||||||
List<Result> failures = checker.test(getTestEntries(42, 0, 0, 0, 0));
|
|
||||||
|
|
||||||
assertThat(failures).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canCheckChangingAssertions_withSingleAssertion() {
|
|
||||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
|
||||||
checker.add(SimpleEntry::isData42, "isData42");
|
|
||||||
checker.checkChangingAssertions();
|
|
||||||
|
|
||||||
List<Result> failures = checker.test(getTestEntries(42, 42, 42, 42, 42));
|
|
||||||
|
|
||||||
assertThat(failures).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canFailCheckChangingAssertions_ifStartingAssertionFails() {
|
|
||||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
|
||||||
checker.add(SimpleEntry::isData42, "isData42");
|
|
||||||
checker.add(SimpleEntry::isData0, "isData0");
|
|
||||||
checker.checkChangingAssertions();
|
|
||||||
|
|
||||||
List<Result> failures = checker.test(getTestEntries(0, 0, 0, 0, 0));
|
|
||||||
|
|
||||||
assertThat(failures).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canFailCheckChangingAssertions_ifStartingAssertionAlwaysPasses() {
|
|
||||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
|
||||||
checker.add(SimpleEntry::isData42, "isData42");
|
|
||||||
checker.add(SimpleEntry::isData0, "isData0");
|
|
||||||
checker.checkChangingAssertions();
|
|
||||||
|
|
||||||
List<Result> failures = checker.test(getTestEntries(0, 0, 0, 0, 0));
|
|
||||||
|
|
||||||
assertThat(failures).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class SimpleEntry implements ITraceEntry {
|
|
||||||
long timestamp;
|
|
||||||
int data;
|
|
||||||
|
|
||||||
SimpleEntry(long timestamp, int data) {
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result isData42() {
|
|
||||||
return new Result(this.data == 42, this.timestamp, "is42", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
Result isData0() {
|
|
||||||
return new Result(this.data == 0, this.timestamp, "is42", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.server.wm.flicker;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import com.android.server.wm.flicker.Assertions.Result;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains {@link Assertions} tests.
|
|
||||||
* To run this test: {@code atest FlickerLibTest:AssertionsTest}
|
|
||||||
*/
|
|
||||||
public class AssertionsTest {
|
|
||||||
@Test
|
|
||||||
public void traceEntryAssertionCanNegateResult() {
|
|
||||||
Assertions.TraceAssertion<Integer> assertNumEquals42 =
|
|
||||||
getIntegerTraceEntryAssertion();
|
|
||||||
|
|
||||||
assertThat(assertNumEquals42.apply(1).success).isFalse();
|
|
||||||
assertThat(assertNumEquals42.negate().apply(1).success).isTrue();
|
|
||||||
|
|
||||||
assertThat(assertNumEquals42.apply(42).success).isTrue();
|
|
||||||
assertThat(assertNumEquals42.negate().apply(42).success).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resultCanBeNegated() {
|
|
||||||
String reason = "Everything is fine!";
|
|
||||||
Result result = new Result(true, 0, "TestAssert", reason);
|
|
||||||
Result negatedResult = result.negate();
|
|
||||||
assertThat(negatedResult.success).isFalse();
|
|
||||||
assertThat(negatedResult.reason).isEqualTo(reason);
|
|
||||||
assertThat(negatedResult.assertionName).isEqualTo("!TestAssert");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Assertions.TraceAssertion<Integer> getIntegerTraceEntryAssertion() {
|
|
||||||
return (num) -> {
|
|
||||||
if (num == 42) {
|
|
||||||
return new Result(true, "Num equals 42");
|
|
||||||
}
|
|
||||||
return new Result(false, "Num doesn't equal 42, actual:" + num);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import static com.android.server.wm.flicker.LayersTraceSubject.assertThat;
|
|
||||||
import static com.android.server.wm.flicker.TestFileUtils.readTestFile;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
import android.graphics.Rect;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains {@link LayersTraceSubject} tests.
|
|
||||||
* To run this test: {@code atest FlickerLibTest:LayersTraceSubjectTest}
|
|
||||||
*/
|
|
||||||
public class LayersTraceSubjectTest {
|
|
||||||
private static final Rect displayRect = new Rect(0, 0, 1440, 2880);
|
|
||||||
|
|
||||||
private static LayersTrace readLayerTraceFromFile(String relativePath) {
|
|
||||||
try {
|
|
||||||
return LayersTrace.parseFrom(readTestFile(relativePath), Paths.get(relativePath));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCanDetectEmptyRegionFromLayerTrace() {
|
|
||||||
LayersTrace layersTraceEntries = readLayerTraceFromFile("layers_trace_emptyregion.pb");
|
|
||||||
try {
|
|
||||||
assertThat(layersTraceEntries).coversRegion(displayRect).forAllEntries();
|
|
||||||
fail("Assertion passed");
|
|
||||||
} catch (AssertionError e) {
|
|
||||||
assertWithMessage("Contains path to trace")
|
|
||||||
.that(e.getMessage()).contains("layers_trace_emptyregion.pb");
|
|
||||||
assertWithMessage("Contains timestamp")
|
|
||||||
.that(e.getMessage()).contains("0h38m28s8ms");
|
|
||||||
assertWithMessage("Contains assertion function")
|
|
||||||
.that(e.getMessage()).contains("coversRegion");
|
|
||||||
assertWithMessage("Contains debug info")
|
|
||||||
.that(e.getMessage()).contains("Region to test: " + displayRect);
|
|
||||||
assertWithMessage("Contains debug info")
|
|
||||||
.that(e.getMessage()).contains("first empty point: 0, 99");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCanDetectIncorrectVisibilityFromLayerTrace() {
|
|
||||||
LayersTrace layersTraceEntries = readLayerTraceFromFile(
|
|
||||||
"layers_trace_invalid_layer_visibility.pb");
|
|
||||||
try {
|
|
||||||
assertThat(layersTraceEntries).showsLayer("com.android.server.wm.flicker.testapp")
|
|
||||||
.then().hidesLayer("com.android.server.wm.flicker.testapp").forAllEntries();
|
|
||||||
fail("Assertion passed");
|
|
||||||
} catch (AssertionError e) {
|
|
||||||
assertWithMessage("Contains path to trace")
|
|
||||||
.that(e.getMessage()).contains("layers_trace_invalid_layer_visibility.pb");
|
|
||||||
assertWithMessage("Contains timestamp")
|
|
||||||
.that(e.getMessage()).contains("70h13m14s303ms");
|
|
||||||
assertWithMessage("Contains assertion function")
|
|
||||||
.that(e.getMessage()).contains("!isVisible");
|
|
||||||
assertWithMessage("Contains debug info")
|
|
||||||
.that(e.getMessage()).contains(
|
|
||||||
"com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp"
|
|
||||||
+ ".SimpleActivity#0 is visible");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,230 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import static com.android.server.wm.flicker.TestFileUtils.readTestFile;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains {@link LayersTrace} tests.
|
|
||||||
* To run this test: {@code atest FlickerLibTest:LayersTraceTest}
|
|
||||||
*/
|
|
||||||
public class LayersTraceTest {
|
|
||||||
private static LayersTrace readLayerTraceFromFile(String relativePath) {
|
|
||||||
try {
|
|
||||||
return LayersTrace.parseFrom(readTestFile(relativePath));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Rect getDisplayBounds() {
|
|
||||||
Point display = new Point();
|
|
||||||
WindowManager wm =
|
|
||||||
(WindowManager) InstrumentationRegistry.getContext().getSystemService(
|
|
||||||
Context.WINDOW_SERVICE);
|
|
||||||
wm.getDefaultDisplay().getRealSize(display);
|
|
||||||
return new Rect(0, 0, display.x, display.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canParseAllLayers() {
|
|
||||||
LayersTrace trace = readLayerTraceFromFile(
|
|
||||||
"layers_trace_emptyregion.pb");
|
|
||||||
assertThat(trace.getEntries()).isNotEmpty();
|
|
||||||
assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L);
|
|
||||||
assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp())
|
|
||||||
.isEqualTo(2308521813510L);
|
|
||||||
List<LayersTrace.Layer> flattenedLayers = trace.getEntries().get(0).asFlattenedLayers();
|
|
||||||
String msg = "Layers:\n" + flattenedLayers.stream().map(layer -> layer.mProto.name)
|
|
||||||
.collect(Collectors.joining("\n\t"));
|
|
||||||
assertWithMessage(msg).that(flattenedLayers).hasSize(47);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canParseVisibleLayers() {
|
|
||||||
LayersTrace trace = readLayerTraceFromFile(
|
|
||||||
"layers_trace_emptyregion.pb");
|
|
||||||
assertThat(trace.getEntries()).isNotEmpty();
|
|
||||||
assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L);
|
|
||||||
assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp())
|
|
||||||
.isEqualTo(2308521813510L);
|
|
||||||
List<LayersTrace.Layer> flattenedLayers = trace.getEntries().get(0).asFlattenedLayers();
|
|
||||||
List<LayersTrace.Layer> visibleLayers = flattenedLayers.stream()
|
|
||||||
.filter(layer -> layer.isVisible() && !layer.isHiddenByParent())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
String msg = "Visible Layers:\n" + visibleLayers.stream()
|
|
||||||
.map(layer -> layer.mProto.name)
|
|
||||||
.collect(Collectors.joining("\n\t"));
|
|
||||||
|
|
||||||
assertWithMessage(msg).that(visibleLayers).hasSize(9);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canParseLayerHierarchy() {
|
|
||||||
LayersTrace trace = readLayerTraceFromFile(
|
|
||||||
"layers_trace_emptyregion.pb");
|
|
||||||
assertThat(trace.getEntries()).isNotEmpty();
|
|
||||||
assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L);
|
|
||||||
assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp())
|
|
||||||
.isEqualTo(2308521813510L);
|
|
||||||
List<LayersTrace.Layer> layers = trace.getEntries().get(0).getRootLayers();
|
|
||||||
assertThat(layers).hasSize(2);
|
|
||||||
assertThat(layers.get(0).mChildren).hasSize(layers.get(0).mProto.children.length);
|
|
||||||
assertThat(layers.get(1).mChildren).hasSize(layers.get(1).mProto.children.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// b/76099859
|
|
||||||
@Test
|
|
||||||
public void canDetectOrphanLayers() {
|
|
||||||
try {
|
|
||||||
readLayerTraceFromFile(
|
|
||||||
"layers_trace_orphanlayers.pb");
|
|
||||||
fail("Failed to detect orphaned layers.");
|
|
||||||
} catch (RuntimeException exception) {
|
|
||||||
assertThat(exception.getMessage()).contains(
|
|
||||||
"Failed to parse layers trace. Found orphan layers "
|
|
||||||
+ "with parent layer id:1006 : 49");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// b/75276931
|
|
||||||
@Test
|
|
||||||
public void canDetectUncoveredRegion() {
|
|
||||||
LayersTrace trace = readLayerTraceFromFile(
|
|
||||||
"layers_trace_emptyregion.pb");
|
|
||||||
LayersTrace.Entry entry = trace.getEntry(2308008331271L);
|
|
||||||
|
|
||||||
Assertions.Result result = entry.coversRegion(getDisplayBounds());
|
|
||||||
|
|
||||||
assertThat(result.failed()).isTrue();
|
|
||||||
assertThat(result.reason).contains("Region to test: Rect(0, 0 - 1440, 2880)");
|
|
||||||
assertThat(result.reason).contains("first empty point: 0, 99");
|
|
||||||
assertThat(result.reason).contains("visible regions:");
|
|
||||||
assertWithMessage("Reason contains list of visible regions")
|
|
||||||
.that(result.reason).contains("StatusBar#0Rect(0, 0 - 1440, 98");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visible region tests
|
|
||||||
@Test
|
|
||||||
public void canTestLayerVisibleRegion_layerDoesNotExist() {
|
|
||||||
LayersTrace trace = readLayerTraceFromFile(
|
|
||||||
"layers_trace_emptyregion.pb");
|
|
||||||
LayersTrace.Entry entry = trace.getEntry(2308008331271L);
|
|
||||||
|
|
||||||
final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1);
|
|
||||||
Assertions.Result result = entry.hasVisibleRegion("ImaginaryLayer",
|
|
||||||
expectedVisibleRegion);
|
|
||||||
|
|
||||||
assertThat(result.failed()).isTrue();
|
|
||||||
assertThat(result.reason).contains("Could not find ImaginaryLayer");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canTestLayerVisibleRegion_layerDoesNotHaveExpectedVisibleRegion() {
|
|
||||||
LayersTrace trace = readLayerTraceFromFile(
|
|
||||||
"layers_trace_emptyregion.pb");
|
|
||||||
LayersTrace.Entry entry = trace.getEntry(2307993020072L);
|
|
||||||
|
|
||||||
final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1);
|
|
||||||
Assertions.Result result = entry.hasVisibleRegion("NexusLauncherActivity#2",
|
|
||||||
expectedVisibleRegion);
|
|
||||||
|
|
||||||
assertThat(result.failed()).isTrue();
|
|
||||||
assertThat(result.reason).contains(
|
|
||||||
"Layer com.google.android.apps.nexuslauncher/com.google.android.apps"
|
|
||||||
+ ".nexuslauncher.NexusLauncherActivity#2 is invisible: activeBuffer=null"
|
|
||||||
+ " type != ColorLayer flags=1 (FLAG_HIDDEN set) visible region is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canTestLayerVisibleRegion_layerIsHiddenByParent() {
|
|
||||||
LayersTrace trace = readLayerTraceFromFile(
|
|
||||||
"layers_trace_emptyregion.pb");
|
|
||||||
LayersTrace.Entry entry = trace.getEntry(2308455948035L);
|
|
||||||
|
|
||||||
final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1);
|
|
||||||
Assertions.Result result = entry.hasVisibleRegion(
|
|
||||||
"SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main",
|
|
||||||
expectedVisibleRegion);
|
|
||||||
|
|
||||||
assertThat(result.failed()).isTrue();
|
|
||||||
assertThat(result.reason).contains(
|
|
||||||
"Layer SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0 is "
|
|
||||||
+ "hidden by parent: com.android.chrome/com.google.android.apps.chrome"
|
|
||||||
+ ".Main#0");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canTestLayerVisibleRegion_incorrectRegionSize() {
|
|
||||||
LayersTrace trace = readLayerTraceFromFile(
|
|
||||||
"layers_trace_emptyregion.pb");
|
|
||||||
LayersTrace.Entry entry = trace.getEntry(2308008331271L);
|
|
||||||
|
|
||||||
final Rect expectedVisibleRegion = new Rect(0, 0, 1440, 99);
|
|
||||||
Assertions.Result result = entry.hasVisibleRegion(
|
|
||||||
"StatusBar",
|
|
||||||
expectedVisibleRegion);
|
|
||||||
|
|
||||||
assertThat(result.failed()).isTrue();
|
|
||||||
assertThat(result.reason).contains("StatusBar#0 has visible "
|
|
||||||
+ "region:Rect(0, 0 - 1440, 98) expected:Rect(0, 0 - 1440, 99)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canTestLayerVisibleRegion() {
|
|
||||||
LayersTrace trace = readLayerTraceFromFile(
|
|
||||||
"layers_trace_emptyregion.pb");
|
|
||||||
LayersTrace.Entry entry = trace.getEntry(2308008331271L);
|
|
||||||
|
|
||||||
final Rect expectedVisibleRegion = new Rect(0, 0, 1440, 98);
|
|
||||||
Assertions.Result result = entry.hasVisibleRegion("StatusBar", expectedVisibleRegion);
|
|
||||||
|
|
||||||
assertThat(result.passed()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canTestLayerVisibleRegion_layerIsNotVisible() {
|
|
||||||
LayersTrace trace = readLayerTraceFromFile(
|
|
||||||
"layers_trace_invalid_layer_visibility.pb");
|
|
||||||
LayersTrace.Entry entry = trace.getEntry(252794268378458L);
|
|
||||||
|
|
||||||
Assertions.Result result = entry.isVisible("com.android.server.wm.flicker.testapp");
|
|
||||||
assertThat(result.failed()).isTrue();
|
|
||||||
assertThat(result.reason).contains(
|
|
||||||
"Layer com.android.server.wm.flicker.testapp/com.android.server.wm.flicker"
|
|
||||||
+ ".testapp.SimpleActivity#0 is invisible: type != ColorLayer visible "
|
|
||||||
+ "region is empty");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
|
||||||
|
|
||||||
import com.google.common.io.ByteStreams;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper functions for test file resources.
|
|
||||||
*/
|
|
||||||
class TestFileUtils {
|
|
||||||
static byte[] readTestFile(String relativePath) throws Exception {
|
|
||||||
Context context = InstrumentationRegistry.getContext();
|
|
||||||
InputStream in = context.getResources().getAssets().open("testdata/" + relativePath);
|
|
||||||
return ByteStreams.toByteArray(in);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,258 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
|
||||||
import static org.mockito.Mockito.inOrder;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
|
||||||
|
|
||||||
import android.os.Environment;
|
|
||||||
|
|
||||||
import com.android.server.wm.flicker.TransitionRunner.TransitionBuilder;
|
|
||||||
import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
|
|
||||||
import com.android.server.wm.flicker.monitor.LayersTraceMonitor;
|
|
||||||
import com.android.server.wm.flicker.monitor.ScreenRecorder;
|
|
||||||
import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor;
|
|
||||||
import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.InOrder;
|
|
||||||
import org.mockito.InjectMocks;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains {@link TransitionRunner} tests.
|
|
||||||
* {@code atest FlickerLibTest:TransitionRunnerTest}
|
|
||||||
*/
|
|
||||||
public class TransitionRunnerTest {
|
|
||||||
@Mock
|
|
||||||
private SimpleUiTransitions mTransitionsMock;
|
|
||||||
@Mock
|
|
||||||
private ScreenRecorder mScreenRecorderMock;
|
|
||||||
@Mock
|
|
||||||
private WindowManagerTraceMonitor mWindowManagerTraceMonitorMock;
|
|
||||||
@Mock
|
|
||||||
private LayersTraceMonitor mLayersTraceMonitorMock;
|
|
||||||
@Mock
|
|
||||||
private WindowAnimationFrameStatsMonitor mWindowAnimationFrameStatsMonitor;
|
|
||||||
@InjectMocks
|
|
||||||
private TransitionBuilder mTransitionBuilder;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void init() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void transitionsRunInOrder() {
|
|
||||||
TransitionRunner.newBuilder()
|
|
||||||
.runBeforeAll(mTransitionsMock::turnOnDevice)
|
|
||||||
.runBefore(mTransitionsMock::openApp)
|
|
||||||
.run(mTransitionsMock::performMagic)
|
|
||||||
.runAfter(mTransitionsMock::closeApp)
|
|
||||||
.runAfterAll(mTransitionsMock::cleanUpTracks)
|
|
||||||
.skipLayersTrace()
|
|
||||||
.skipWindowManagerTrace()
|
|
||||||
.build()
|
|
||||||
.run();
|
|
||||||
|
|
||||||
InOrder orderVerifier = inOrder(mTransitionsMock);
|
|
||||||
orderVerifier.verify(mTransitionsMock).turnOnDevice();
|
|
||||||
orderVerifier.verify(mTransitionsMock).openApp();
|
|
||||||
orderVerifier.verify(mTransitionsMock).performMagic();
|
|
||||||
orderVerifier.verify(mTransitionsMock).closeApp();
|
|
||||||
orderVerifier.verify(mTransitionsMock).cleanUpTracks();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canCombineTransitions() {
|
|
||||||
TransitionRunner.newBuilder()
|
|
||||||
.runBeforeAll(mTransitionsMock::turnOnDevice)
|
|
||||||
.runBeforeAll(mTransitionsMock::turnOnDevice)
|
|
||||||
.runBefore(mTransitionsMock::openApp)
|
|
||||||
.runBefore(mTransitionsMock::openApp)
|
|
||||||
.run(mTransitionsMock::performMagic)
|
|
||||||
.run(mTransitionsMock::performMagic)
|
|
||||||
.runAfter(mTransitionsMock::closeApp)
|
|
||||||
.runAfter(mTransitionsMock::closeApp)
|
|
||||||
.runAfterAll(mTransitionsMock::cleanUpTracks)
|
|
||||||
.runAfterAll(mTransitionsMock::cleanUpTracks)
|
|
||||||
.skipLayersTrace()
|
|
||||||
.skipWindowManagerTrace()
|
|
||||||
.build()
|
|
||||||
.run();
|
|
||||||
|
|
||||||
final int wantedNumberOfInvocations = 2;
|
|
||||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).turnOnDevice();
|
|
||||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).openApp();
|
|
||||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).performMagic();
|
|
||||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).closeApp();
|
|
||||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).cleanUpTracks();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void emptyTransitionPasses() {
|
|
||||||
List<TransitionResult> results = TransitionRunner.newBuilder()
|
|
||||||
.skipLayersTrace()
|
|
||||||
.skipWindowManagerTrace()
|
|
||||||
.build()
|
|
||||||
.run()
|
|
||||||
.getResults();
|
|
||||||
assertThat(results).hasSize(1);
|
|
||||||
assertThat(results.get(0).layersTraceExists()).isFalse();
|
|
||||||
assertThat(results.get(0).windowManagerTraceExists()).isFalse();
|
|
||||||
assertThat(results.get(0).screenCaptureVideoExists()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canRepeatTransitions() {
|
|
||||||
final int wantedNumberOfInvocations = 10;
|
|
||||||
TransitionRunner.newBuilder()
|
|
||||||
.runBeforeAll(mTransitionsMock::turnOnDevice)
|
|
||||||
.runBefore(mTransitionsMock::openApp)
|
|
||||||
.run(mTransitionsMock::performMagic)
|
|
||||||
.runAfter(mTransitionsMock::closeApp)
|
|
||||||
.runAfterAll(mTransitionsMock::cleanUpTracks)
|
|
||||||
.repeat(wantedNumberOfInvocations)
|
|
||||||
.skipLayersTrace()
|
|
||||||
.skipWindowManagerTrace()
|
|
||||||
.build()
|
|
||||||
.run();
|
|
||||||
verify(mTransitionsMock).turnOnDevice();
|
|
||||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).openApp();
|
|
||||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).performMagic();
|
|
||||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).closeApp();
|
|
||||||
verify(mTransitionsMock).cleanUpTracks();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void emptyTask() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canCaptureWindowManagerTrace() {
|
|
||||||
mTransitionBuilder
|
|
||||||
.run(this::emptyTask)
|
|
||||||
.includeJankyRuns()
|
|
||||||
.skipLayersTrace()
|
|
||||||
.withTag("mCaptureWmTraceTransitionRunner")
|
|
||||||
.build().run();
|
|
||||||
InOrder orderVerifier = inOrder(mWindowManagerTraceMonitorMock);
|
|
||||||
orderVerifier.verify(mWindowManagerTraceMonitorMock).start();
|
|
||||||
orderVerifier.verify(mWindowManagerTraceMonitorMock).stop();
|
|
||||||
orderVerifier.verify(mWindowManagerTraceMonitorMock)
|
|
||||||
.save("mCaptureWmTraceTransitionRunner", 0);
|
|
||||||
verifyNoMoreInteractions(mWindowManagerTraceMonitorMock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canCaptureLayersTrace() {
|
|
||||||
mTransitionBuilder
|
|
||||||
.run(this::emptyTask)
|
|
||||||
.includeJankyRuns()
|
|
||||||
.skipWindowManagerTrace()
|
|
||||||
.withTag("mCaptureLayersTraceTransitionRunner")
|
|
||||||
.build().run();
|
|
||||||
InOrder orderVerifier = inOrder(mLayersTraceMonitorMock);
|
|
||||||
orderVerifier.verify(mLayersTraceMonitorMock).start();
|
|
||||||
orderVerifier.verify(mLayersTraceMonitorMock).stop();
|
|
||||||
orderVerifier.verify(mLayersTraceMonitorMock)
|
|
||||||
.save("mCaptureLayersTraceTransitionRunner", 0);
|
|
||||||
verifyNoMoreInteractions(mLayersTraceMonitorMock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canRecordEachRun() throws IOException {
|
|
||||||
mTransitionBuilder
|
|
||||||
.run(this::emptyTask)
|
|
||||||
.withTag("mRecordEachRun")
|
|
||||||
.recordEachRun()
|
|
||||||
.includeJankyRuns()
|
|
||||||
.skipLayersTrace()
|
|
||||||
.skipWindowManagerTrace()
|
|
||||||
.repeat(2)
|
|
||||||
.build().run();
|
|
||||||
InOrder orderVerifier = inOrder(mScreenRecorderMock);
|
|
||||||
orderVerifier.verify(mScreenRecorderMock).start();
|
|
||||||
orderVerifier.verify(mScreenRecorderMock).stop();
|
|
||||||
orderVerifier.verify(mScreenRecorderMock).save("mRecordEachRun", 0);
|
|
||||||
orderVerifier.verify(mScreenRecorderMock).start();
|
|
||||||
orderVerifier.verify(mScreenRecorderMock).stop();
|
|
||||||
orderVerifier.verify(mScreenRecorderMock).save("mRecordEachRun", 1);
|
|
||||||
verifyNoMoreInteractions(mScreenRecorderMock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canRecordAllRuns() throws IOException {
|
|
||||||
doReturn(Paths.get(Environment.getExternalStorageDirectory().getAbsolutePath(),
|
|
||||||
"mRecordAllRuns.mp4")).when(mScreenRecorderMock).save("mRecordAllRuns");
|
|
||||||
mTransitionBuilder
|
|
||||||
.run(this::emptyTask)
|
|
||||||
.recordAllRuns()
|
|
||||||
.includeJankyRuns()
|
|
||||||
.skipLayersTrace()
|
|
||||||
.skipWindowManagerTrace()
|
|
||||||
.withTag("mRecordAllRuns")
|
|
||||||
.repeat(2)
|
|
||||||
.build().run();
|
|
||||||
InOrder orderVerifier = inOrder(mScreenRecorderMock);
|
|
||||||
orderVerifier.verify(mScreenRecorderMock).start();
|
|
||||||
orderVerifier.verify(mScreenRecorderMock).stop();
|
|
||||||
orderVerifier.verify(mScreenRecorderMock).save("mRecordAllRuns");
|
|
||||||
verifyNoMoreInteractions(mScreenRecorderMock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canSkipJankyRuns() {
|
|
||||||
doReturn(false).doReturn(true).doReturn(false)
|
|
||||||
.when(mWindowAnimationFrameStatsMonitor).jankyFramesDetected();
|
|
||||||
List<TransitionResult> results = mTransitionBuilder
|
|
||||||
.run(this::emptyTask)
|
|
||||||
.skipLayersTrace()
|
|
||||||
.skipWindowManagerTrace()
|
|
||||||
.repeat(3)
|
|
||||||
.build().run().getResults();
|
|
||||||
assertThat(results).hasSize(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SimpleUiTransitions {
|
|
||||||
public void turnOnDevice() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void openApp() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void performMagic() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void closeApp() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cleanUpTracks() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import static com.android.server.wm.flicker.TestFileUtils.readTestFile;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import com.android.server.wm.flicker.Assertions.Result;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains {@link WindowManagerTrace} tests.
|
|
||||||
* To run this test: {@code atest FlickerLibTest:WindowManagerTraceTest}
|
|
||||||
*/
|
|
||||||
public class WindowManagerTraceTest {
|
|
||||||
private WindowManagerTrace mTrace;
|
|
||||||
|
|
||||||
private static WindowManagerTrace readWindowManagerTraceFromFile(String relativePath) {
|
|
||||||
try {
|
|
||||||
return WindowManagerTrace.parseFrom(readTestFile(relativePath));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
mTrace = readWindowManagerTraceFromFile("wm_trace_openchrome.pb");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canParseAllEntries() {
|
|
||||||
assertThat(mTrace.getEntries().get(0).getTimestamp()).isEqualTo(241777211939236L);
|
|
||||||
assertThat(mTrace.getEntries().get(mTrace.getEntries().size() - 1).getTimestamp()).isEqualTo
|
|
||||||
(241779809471942L);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canDetectAboveAppWindowVisibility() {
|
|
||||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
|
|
||||||
Result result = entry.isAboveAppWindowVisible("NavigationBar");
|
|
||||||
assertThat(result.passed()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canDetectBelowAppWindowVisibility() {
|
|
||||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
|
|
||||||
Result result = entry.isBelowAppWindowVisible("wallpaper");
|
|
||||||
assertThat(result.passed()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canDetectAppWindowVisibility() {
|
|
||||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
|
|
||||||
Result result = entry.isAppWindowVisible("com.google.android.apps.nexuslauncher");
|
|
||||||
assertThat(result.passed()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canFailWithReasonForVisibilityChecks_windowNotFound() {
|
|
||||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
|
|
||||||
Result result = entry.isAboveAppWindowVisible("ImaginaryWindow");
|
|
||||||
assertThat(result.failed()).isTrue();
|
|
||||||
assertThat(result.reason).contains("ImaginaryWindow cannot be found");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canFailWithReasonForVisibilityChecks_windowNotVisible() {
|
|
||||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
|
|
||||||
Result result = entry.isAboveAppWindowVisible("AssistPreviewPanel");
|
|
||||||
assertThat(result.failed()).isTrue();
|
|
||||||
assertThat(result.reason).contains("AssistPreviewPanel is invisible");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canDetectAppZOrder() {
|
|
||||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241778130296410L);
|
|
||||||
Result result = entry.isVisibleAppWindowOnTop("com.google.android.apps.chrome");
|
|
||||||
assertThat(result.passed()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canFailWithReasonForZOrderChecks_windowNotOnTop() {
|
|
||||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241778130296410L);
|
|
||||||
Result result = entry.isVisibleAppWindowOnTop("com.google.android.apps.nexuslauncher");
|
|
||||||
assertThat(result.failed()).isTrue();
|
|
||||||
assertThat(result.reason).contains("wanted=com.google.android.apps.nexuslauncher");
|
|
||||||
assertThat(result.reason).contains("found=com.android.chrome/"
|
|
||||||
+ "com.google.android.apps.chrome.Main");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import static com.android.server.wm.flicker.TestFileUtils.readTestFile;
|
|
||||||
import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains {@link WmTraceSubject} tests.
|
|
||||||
* To run this test: {@code atest FlickerLibTest:WmTraceSubjectTest}
|
|
||||||
*/
|
|
||||||
public class WmTraceSubjectTest {
|
|
||||||
private static WindowManagerTrace readWmTraceFromFile(String relativePath) {
|
|
||||||
try {
|
|
||||||
return WindowManagerTrace.parseFrom(readTestFile(relativePath));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCanTransitionInAppWindow() {
|
|
||||||
WindowManagerTrace trace = readWmTraceFromFile("wm_trace_openchrome2.pb");
|
|
||||||
|
|
||||||
assertThat(trace).showsAppWindowOnTop("com.google.android.apps.nexuslauncher/"
|
|
||||||
+ ".NexusLauncherActivity").forRange(174684850717208L, 174685957511016L);
|
|
||||||
assertThat(trace).showsAppWindowOnTop(
|
|
||||||
"com.google.android.apps.nexuslauncher/.NexusLauncherActivity")
|
|
||||||
.then()
|
|
||||||
.showsAppWindowOnTop("com.android.chrome")
|
|
||||||
.forAllEntries();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.server.wm.flicker.monitor;
|
|
||||||
|
|
||||||
import static android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto.MAGIC_NUMBER_H;
|
|
||||||
import static android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto.MAGIC_NUMBER_L;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto;
|
|
||||||
|
|
||||||
import com.google.common.io.Files;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains {@link LayersTraceMonitor} tests.
|
|
||||||
* To run this test: {@code atest FlickerLibTest:LayersTraceMonitorTest}
|
|
||||||
*/
|
|
||||||
public class LayersTraceMonitorTest {
|
|
||||||
private LayersTraceMonitor mLayersTraceMonitor;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
mLayersTraceMonitor = new LayersTraceMonitor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void teardown() {
|
|
||||||
mLayersTraceMonitor.stop();
|
|
||||||
mLayersTraceMonitor.getOutputTraceFilePath("captureLayersTrace").toFile().delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canStartLayersTrace() throws Exception {
|
|
||||||
mLayersTraceMonitor.start();
|
|
||||||
assertThat(mLayersTraceMonitor.isEnabled()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canStopLayersTrace() throws Exception {
|
|
||||||
mLayersTraceMonitor.start();
|
|
||||||
assertThat(mLayersTraceMonitor.isEnabled()).isTrue();
|
|
||||||
mLayersTraceMonitor.stop();
|
|
||||||
assertThat(mLayersTraceMonitor.isEnabled()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void captureLayersTrace() throws Exception {
|
|
||||||
mLayersTraceMonitor.start();
|
|
||||||
mLayersTraceMonitor.stop();
|
|
||||||
File testFile = mLayersTraceMonitor.save("captureLayersTrace").toFile();
|
|
||||||
assertThat(testFile.exists()).isTrue();
|
|
||||||
byte[] trace = Files.toByteArray(testFile);
|
|
||||||
assertThat(trace.length).isGreaterThan(0);
|
|
||||||
LayersTraceFileProto mLayerTraceFileProto = LayersTraceFileProto.parseFrom(trace);
|
|
||||||
assertThat(mLayerTraceFileProto.magicNumber).isEqualTo(
|
|
||||||
(long) MAGIC_NUMBER_H << 32 | MAGIC_NUMBER_L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +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.server.wm.flicker.monitor;
|
|
||||||
|
|
||||||
import static android.os.SystemClock.sleep;
|
|
||||||
|
|
||||||
import static com.android.server.wm.flicker.monitor.ScreenRecorder.DEFAULT_OUTPUT_PATH;
|
|
||||||
import static com.android.server.wm.flicker.monitor.ScreenRecorder.getPath;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains {@link ScreenRecorder} tests.
|
|
||||||
* To run this test: {@code atest FlickerLibTest:ScreenRecorderTest}
|
|
||||||
*/
|
|
||||||
public class ScreenRecorderTest {
|
|
||||||
private static final String TEST_VIDEO_FILENAME = "test.mp4";
|
|
||||||
private ScreenRecorder mScreenRecorder;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
mScreenRecorder = new ScreenRecorder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void teardown() {
|
|
||||||
DEFAULT_OUTPUT_PATH.toFile().delete();
|
|
||||||
getPath(TEST_VIDEO_FILENAME).toFile().delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void videoIsRecorded() {
|
|
||||||
mScreenRecorder.start();
|
|
||||||
sleep(100);
|
|
||||||
mScreenRecorder.stop();
|
|
||||||
File file = DEFAULT_OUTPUT_PATH.toFile();
|
|
||||||
assertThat(file.exists()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void videoCanBeSaved() {
|
|
||||||
mScreenRecorder.start();
|
|
||||||
sleep(100);
|
|
||||||
mScreenRecorder.stop();
|
|
||||||
mScreenRecorder.save(TEST_VIDEO_FILENAME);
|
|
||||||
File file = getPath(TEST_VIDEO_FILENAME).toFile();
|
|
||||||
assertThat(file.exists()).isTrue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +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.server.wm.flicker.monitor;
|
|
||||||
|
|
||||||
import static com.android.server.wm.flicker.helpers.AutomationUtils.wakeUpAndGoToHomeScreen;
|
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains {@link WindowAnimationFrameStatsMonitor} tests.
|
|
||||||
* To run this test: {@code atest FlickerLibTest:WindowAnimationFrameStatsMonitorTest}
|
|
||||||
*/
|
|
||||||
public class WindowAnimationFrameStatsMonitorTest {
|
|
||||||
private WindowAnimationFrameStatsMonitor mWindowAnimationFrameStatsMonitor;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
mWindowAnimationFrameStatsMonitor = new WindowAnimationFrameStatsMonitor(
|
|
||||||
InstrumentationRegistry.getInstrumentation());
|
|
||||||
wakeUpAndGoToHomeScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(vishnun)
|
|
||||||
@Ignore("Disabled until app-helper libraries are available.")
|
|
||||||
@Test
|
|
||||||
public void captureWindowAnimationFrameStats() throws Exception {
|
|
||||||
mWindowAnimationFrameStatsMonitor.start();
|
|
||||||
//AppHelperWrapper.getInstance().getHelper(CHROME).open();
|
|
||||||
//AppHelperWrapper.getInstance().getHelper(CHROME).exit();
|
|
||||||
mWindowAnimationFrameStatsMonitor.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +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.server.wm.flicker.monitor;
|
|
||||||
|
|
||||||
import static com.android.server.wm.nano.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
|
|
||||||
import static com.android.server.wm.nano.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import com.android.server.wm.nano.WindowManagerTraceFileProto;
|
|
||||||
|
|
||||||
import com.google.common.io.Files;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains {@link WindowManagerTraceMonitor} tests.
|
|
||||||
* To run this test: {@code atest FlickerLibTest:WindowManagerTraceMonitorTest}
|
|
||||||
*/
|
|
||||||
public class WindowManagerTraceMonitorTest {
|
|
||||||
private WindowManagerTraceMonitor mWindowManagerTraceMonitor;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
mWindowManagerTraceMonitor = new WindowManagerTraceMonitor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void teardown() {
|
|
||||||
mWindowManagerTraceMonitor.stop();
|
|
||||||
mWindowManagerTraceMonitor.getOutputTraceFilePath("captureWindowTrace").toFile().delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canStartWindowTrace() throws Exception {
|
|
||||||
mWindowManagerTraceMonitor.start();
|
|
||||||
assertThat(mWindowManagerTraceMonitor.isEnabled()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canStopWindowTrace() throws Exception {
|
|
||||||
mWindowManagerTraceMonitor.start();
|
|
||||||
assertThat(mWindowManagerTraceMonitor.isEnabled()).isTrue();
|
|
||||||
mWindowManagerTraceMonitor.stop();
|
|
||||||
assertThat(mWindowManagerTraceMonitor.isEnabled()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void captureWindowTrace() throws Exception {
|
|
||||||
mWindowManagerTraceMonitor.start();
|
|
||||||
mWindowManagerTraceMonitor.stop();
|
|
||||||
File testFile = mWindowManagerTraceMonitor.save("captureWindowTrace").toFile();
|
|
||||||
assertThat(testFile.exists()).isTrue();
|
|
||||||
byte[] trace = Files.toByteArray(testFile);
|
|
||||||
assertThat(trace.length).isGreaterThan(0);
|
|
||||||
WindowManagerTraceFileProto mWindowTraceFileProto = WindowManagerTraceFileProto.parseFrom(
|
|
||||||
trace);
|
|
||||||
assertThat(mWindowTraceFileProto.magicNumber).isEqualTo(
|
|
||||||
(long) MAGIC_NUMBER_H << 32 | MAGIC_NUMBER_L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -29,11 +29,15 @@ import android.util.Log;
|
|||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
import androidx.test.filters.FlakyTest;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
import org.junit.runners.Parameterized;
|
import org.junit.runners.Parameterized;
|
||||||
import org.junit.runners.Parameterized.Parameters;
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
@@ -44,18 +48,19 @@ import java.util.Collection;
|
|||||||
* Cycle through supported app rotations.
|
* Cycle through supported app rotations.
|
||||||
* To run this test: {@code atest FlickerTest:ChangeAppRotationTest}
|
* To run this test: {@code atest FlickerTest:ChangeAppRotationTest}
|
||||||
*/
|
*/
|
||||||
@RunWith(Parameterized.class)
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class ChangeAppRotationTest extends FlickerTestBase {
|
public class ChangeAppRotationTest extends FlickerTestBase {
|
||||||
private int beginRotation;
|
private int mBeginRotation;
|
||||||
private int endRotation;
|
private int mEndRotation;
|
||||||
|
|
||||||
public ChangeAppRotationTest(String beginRotationName, String endRotationName,
|
public ChangeAppRotationTest(String beginRotationName, String endRotationName,
|
||||||
int beginRotation, int endRotation) {
|
int beginRotation, int endRotation) {
|
||||||
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||||
this.beginRotation = beginRotation;
|
this.mBeginRotation = beginRotation;
|
||||||
this.endRotation = endRotation;
|
this.mEndRotation = endRotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parameters(name = "{0}-{1}")
|
@Parameters(name = "{0}-{1}")
|
||||||
@@ -77,15 +82,19 @@ public class ChangeAppRotationTest extends FlickerTestBase {
|
|||||||
@Before
|
@Before
|
||||||
public void runTransition() {
|
public void runTransition() {
|
||||||
super.runTransition(
|
super.runTransition(
|
||||||
changeAppRotation(testApp, uiDevice, beginRotation, endRotation).build());
|
changeAppRotation(mTestApp, mUiDevice, mBeginRotation, mEndRotation).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FlakyTest(bugId = 140855415)
|
||||||
|
@Ignore("Waiting bug feedback")
|
||||||
@Test
|
@Test
|
||||||
public void checkVisibility_navBarWindowIsAlwaysVisible() {
|
public void checkVisibility_navBarWindowIsAlwaysVisible() {
|
||||||
checkResults(result -> assertThat(result)
|
checkResults(result -> assertThat(result)
|
||||||
.showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
|
.showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FlakyTest(bugId = 140855415)
|
||||||
|
@Ignore("Waiting bug feedback")
|
||||||
@Test
|
@Test
|
||||||
public void checkVisibility_statusBarWindowIsAlwaysVisible() {
|
public void checkVisibility_statusBarWindowIsAlwaysVisible() {
|
||||||
checkResults(result -> assertThat(result)
|
checkResults(result -> assertThat(result)
|
||||||
@@ -94,8 +103,8 @@ public class ChangeAppRotationTest extends FlickerTestBase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkPosition_navBarLayerRotatesAndScales() {
|
public void checkPosition_navBarLayerRotatesAndScales() {
|
||||||
Rect startingPos = getNavigationBarPosition(beginRotation);
|
Rect startingPos = getNavigationBarPosition(mBeginRotation);
|
||||||
Rect endingPos = getNavigationBarPosition(endRotation);
|
Rect endingPos = getNavigationBarPosition(mEndRotation);
|
||||||
checkResults(result -> {
|
checkResults(result -> {
|
||||||
LayersTraceSubject.assertThat(result)
|
LayersTraceSubject.assertThat(result)
|
||||||
.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
|
.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
|
||||||
@@ -108,22 +117,22 @@ public class ChangeAppRotationTest extends FlickerTestBase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkPosition_appLayerRotates() {
|
public void checkPosition_appLayerRotates() {
|
||||||
Rect startingPos = getAppPosition(beginRotation);
|
Rect startingPos = getAppPosition(mBeginRotation);
|
||||||
Rect endingPos = getAppPosition(endRotation);
|
Rect endingPos = getAppPosition(mEndRotation);
|
||||||
Log.e(TAG, "startingPos=" + startingPos + " endingPos=" + endingPos);
|
Log.e(TAG, "startingPos=" + startingPos + " endingPos=" + endingPos);
|
||||||
checkResults(result -> {
|
checkResults(result -> {
|
||||||
LayersTraceSubject.assertThat(result)
|
LayersTraceSubject.assertThat(result)
|
||||||
.hasVisibleRegion(testApp.getPackage(), startingPos).inTheBeginning();
|
.hasVisibleRegion(mTestApp.getPackage(), startingPos).inTheBeginning();
|
||||||
LayersTraceSubject.assertThat(result)
|
LayersTraceSubject.assertThat(result)
|
||||||
.hasVisibleRegion(testApp.getPackage(), endingPos).atTheEnd();
|
.hasVisibleRegion(mTestApp.getPackage(), endingPos).atTheEnd();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkPosition_statusBarLayerScales() {
|
public void checkPosition_statusBarLayerScales() {
|
||||||
Rect startingPos = getStatusBarPosition(beginRotation);
|
Rect startingPos = getStatusBarPosition(mBeginRotation);
|
||||||
Rect endingPos = getStatusBarPosition(endRotation);
|
Rect endingPos = getStatusBarPosition(mEndRotation);
|
||||||
checkResults(result -> {
|
checkResults(result -> {
|
||||||
LayersTraceSubject.assertThat(result)
|
LayersTraceSubject.assertThat(result)
|
||||||
.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
|
.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
|
||||||
@@ -134,12 +143,16 @@ public class ChangeAppRotationTest extends FlickerTestBase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FlakyTest(bugId = 140855415)
|
||||||
|
@Ignore("Waiting bug feedback")
|
||||||
@Test
|
@Test
|
||||||
public void checkVisibility_navBarLayerIsAlwaysVisible() {
|
public void checkVisibility_navBarLayerIsAlwaysVisible() {
|
||||||
checkResults(result -> LayersTraceSubject.assertThat(result)
|
checkResults(result -> LayersTraceSubject.assertThat(result)
|
||||||
.showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
|
.showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FlakyTest(bugId = 140855415)
|
||||||
|
@Ignore("Waiting bug feedback")
|
||||||
@Test
|
@Test
|
||||||
public void checkVisibility_statusBarLayerIsAlwaysVisible() {
|
public void checkVisibility_statusBarLayerIsAlwaysVisible() {
|
||||||
checkResults(result -> LayersTraceSubject.assertThat(result)
|
checkResults(result -> LayersTraceSubject.assertThat(result)
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ import androidx.test.filters.LargeTest;
|
|||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test IME window closing back to app window transitions.
|
* Test IME window closing back to app window transitions.
|
||||||
@@ -35,6 +37,7 @@ import org.junit.runner.RunWith;
|
|||||||
*/
|
*/
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class CloseImeWindowToAppTest extends FlickerTestBase {
|
public class CloseImeWindowToAppTest extends FlickerTestBase {
|
||||||
|
|
||||||
private static final String IME_WINDOW_TITLE = "InputMethod";
|
private static final String IME_WINDOW_TITLE = "InputMethod";
|
||||||
@@ -44,7 +47,7 @@ public class CloseImeWindowToAppTest extends FlickerTestBase {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void runTransition() {
|
public void runTransition() {
|
||||||
super.runTransition(editTextLoseFocusToApp(uiDevice)
|
super.runTransition(editTextLoseFocusToApp(mUiDevice)
|
||||||
.includeJankyRuns().build());
|
.includeJankyRuns().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ import androidx.test.filters.LargeTest;
|
|||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test IME window closing to home transitions.
|
* Test IME window closing to home transitions.
|
||||||
@@ -35,6 +37,7 @@ import org.junit.runner.RunWith;
|
|||||||
*/
|
*/
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class CloseImeWindowToHomeTest extends FlickerTestBase {
|
public class CloseImeWindowToHomeTest extends FlickerTestBase {
|
||||||
|
|
||||||
private static final String IME_WINDOW_TITLE = "InputMethod";
|
private static final String IME_WINDOW_TITLE = "InputMethod";
|
||||||
@@ -44,7 +47,7 @@ public class CloseImeWindowToHomeTest extends FlickerTestBase {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void runTransition() {
|
public void runTransition() {
|
||||||
super.runTransition(editTextLoseFocusToHome(uiDevice)
|
super.runTransition(editTextLoseFocusToHome(mUiDevice)
|
||||||
.includeJankyRuns().build());
|
.includeJankyRuns().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class CommonTransitions {
|
|||||||
device.setOrientationNatural();
|
device.setOrientationNatural();
|
||||||
}
|
}
|
||||||
// Wait for animation to complete
|
// Wait for animation to complete
|
||||||
sleep(3000);
|
sleep(1000);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -216,10 +216,10 @@ class CommonTransitions {
|
|||||||
|
|
||||||
static TransitionBuilder resizeSplitScreen(IAppHelper testAppTop, IAppHelper testAppBottom,
|
static TransitionBuilder resizeSplitScreen(IAppHelper testAppTop, IAppHelper testAppBottom,
|
||||||
UiDevice device, Rational startRatio, Rational stopRatio) {
|
UiDevice device, Rational startRatio, Rational stopRatio) {
|
||||||
String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_" +
|
String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_"
|
||||||
testAppBottom.getLauncherName() + "_" +
|
+ testAppBottom.getLauncherName() + "_"
|
||||||
startRatio.toString().replace("/", ":") + "_to_" +
|
+ startRatio.toString().replace("/", ":") + "_to_"
|
||||||
stopRatio.toString().replace("/", ":");
|
+ stopRatio.toString().replace("/", ":");
|
||||||
return TransitionRunner.newBuilder()
|
return TransitionRunner.newBuilder()
|
||||||
.withTag(testTag)
|
.withTag(testTag)
|
||||||
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
|
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
|
||||||
@@ -231,7 +231,7 @@ class CommonTransitions {
|
|||||||
.runBefore(() -> launchSplitScreen(device))
|
.runBefore(() -> launchSplitScreen(device))
|
||||||
.runBefore(() -> {
|
.runBefore(() -> {
|
||||||
UiObject2 snapshot = device.findObject(
|
UiObject2 snapshot = device.findObject(
|
||||||
By.res("com.google.android.apps.nexuslauncher", "snapshot"));
|
By.res(device.getLauncherPackageName(), "snapshot"));
|
||||||
snapshot.click();
|
snapshot.click();
|
||||||
})
|
})
|
||||||
.runBefore(() -> AutomationUtils.resizeSplitScreen(device, startRatio))
|
.runBefore(() -> AutomationUtils.resizeSplitScreen(device, startRatio))
|
||||||
@@ -316,4 +316,4 @@ class CommonTransitions {
|
|||||||
.runAfterAll(testApp::exit)
|
.runAfterAll(testApp::exit)
|
||||||
.repeat(ITERATIONS);
|
.repeat(ITERATIONS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,17 +22,22 @@ import android.util.Rational;
|
|||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
import androidx.test.filters.LargeTest;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests to help debug individual transitions, capture video recordings and create test cases.
|
* Tests to help debug individual transitions, capture video recordings and create test cases.
|
||||||
*/
|
*/
|
||||||
|
@LargeTest
|
||||||
@Ignore("Used for debugging transitions used in FlickerTests.")
|
@Ignore("Used for debugging transitions used in FlickerTests.")
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class DebugTest {
|
public class DebugTest {
|
||||||
private IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
private IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||||
|
|||||||
@@ -16,20 +16,23 @@
|
|||||||
|
|
||||||
package com.android.server.wm.flicker;
|
package com.android.server.wm.flicker;
|
||||||
|
|
||||||
|
import static androidx.test.InstrumentationRegistry.getInstrumentation;
|
||||||
|
|
||||||
import static com.android.server.wm.flicker.helpers.AutomationUtils.setDefaultWait;
|
import static com.android.server.wm.flicker.helpers.AutomationUtils.setDefaultWait;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
import android.platform.helpers.IAppHelper;
|
import android.platform.helpers.IAppHelper;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
import android.support.test.uiautomator.UiDevice;
|
import android.support.test.uiautomator.UiDevice;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
|
||||||
|
|
||||||
import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
|
import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -51,10 +54,16 @@ public class FlickerTestBase {
|
|||||||
static final String DOCKED_STACK_DIVIDER = "DockedStackDivider";
|
static final String DOCKED_STACK_DIVIDER = "DockedStackDivider";
|
||||||
private static HashMap<String, List<TransitionResult>> transitionResults =
|
private static HashMap<String, List<TransitionResult>> transitionResults =
|
||||||
new HashMap<>();
|
new HashMap<>();
|
||||||
IAppHelper testApp;
|
IAppHelper mTestApp;
|
||||||
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
|
UiDevice mUiDevice;
|
||||||
private List<TransitionResult> results;
|
private List<TransitionResult> mResults;
|
||||||
private TransitionResult lastResult = null;
|
private TransitionResult mLastResult = null;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
InstrumentationRegistry.registerInstance(getInstrumentation(), new Bundle());
|
||||||
|
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Teardown any system settings and clean up test artifacts from the file system.
|
* Teardown any system settings and clean up test artifacts from the file system.
|
||||||
@@ -91,14 +100,14 @@ public class FlickerTestBase {
|
|||||||
*/
|
*/
|
||||||
void runTransition(TransitionRunner transition) {
|
void runTransition(TransitionRunner transition) {
|
||||||
if (transitionResults.containsKey(transition.getTestTag())) {
|
if (transitionResults.containsKey(transition.getTestTag())) {
|
||||||
results = transitionResults.get(transition.getTestTag());
|
mResults = transitionResults.get(transition.getTestTag());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
results = transition.run().getResults();
|
mResults = transition.run().getResults();
|
||||||
/* Fail if we don't have any results due to jank */
|
/* Fail if we don't have any results due to jank */
|
||||||
assertWithMessage("No results to test because all transition runs were invalid because "
|
assertWithMessage("No results to test because all transition runs were invalid because "
|
||||||
+ "of Jank").that(results).isNotEmpty();
|
+ "of Jank").that(mResults).isNotEmpty();
|
||||||
transitionResults.put(transition.getTestTag(), results);
|
transitionResults.put(transition.getTestTag(), mResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,11 +115,11 @@ public class FlickerTestBase {
|
|||||||
*/
|
*/
|
||||||
void checkResults(Consumer<TransitionResult> assertion) {
|
void checkResults(Consumer<TransitionResult> assertion) {
|
||||||
|
|
||||||
for (TransitionResult result : results) {
|
for (TransitionResult result : mResults) {
|
||||||
lastResult = result;
|
mLastResult = result;
|
||||||
assertion.accept(result);
|
assertion.accept(result);
|
||||||
}
|
}
|
||||||
lastResult = null;
|
mLastResult = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,8 +128,8 @@ public class FlickerTestBase {
|
|||||||
*/
|
*/
|
||||||
@After
|
@After
|
||||||
public void markArtifactsForSaving() {
|
public void markArtifactsForSaving() {
|
||||||
if (lastResult != null) {
|
if (mLastResult != null) {
|
||||||
lastResult.flagForSaving();
|
mLastResult.flagForSaving();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,16 @@ import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
|
|||||||
import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
|
import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
import androidx.test.filters.FlakyTest;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test cold launch app from launcher.
|
* Test cold launch app from launcher.
|
||||||
@@ -34,16 +38,17 @@ import org.junit.runner.RunWith;
|
|||||||
*/
|
*/
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class OpenAppColdTest extends FlickerTestBase {
|
public class OpenAppColdTest extends FlickerTestBase {
|
||||||
|
|
||||||
public OpenAppColdTest() {
|
public OpenAppColdTest() {
|
||||||
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void runTransition() {
|
public void runTransition() {
|
||||||
super.runTransition(getOpenAppCold(testApp, uiDevice).build());
|
super.runTransition(getOpenAppCold(mTestApp, mUiDevice).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -61,9 +66,9 @@ public class OpenAppColdTest extends FlickerTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void checkVisibility_wallpaperWindowBecomesInvisible() {
|
public void checkVisibility_wallpaperWindowBecomesInvisible() {
|
||||||
checkResults(result -> assertThat(result)
|
checkResults(result -> assertThat(result)
|
||||||
.showsBelowAppWindow("wallpaper")
|
.showsBelowAppWindow("Wallpaper")
|
||||||
.then()
|
.then()
|
||||||
.hidesBelowAppWindow("wallpaper")
|
.hidesBelowAppWindow("Wallpaper")
|
||||||
.forAllEntries());
|
.forAllEntries());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,13 +76,15 @@ public class OpenAppColdTest extends FlickerTestBase {
|
|||||||
public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
|
public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
|
||||||
checkResults(result -> assertThat(result)
|
checkResults(result -> assertThat(result)
|
||||||
.showsAppWindowOnTop(
|
.showsAppWindowOnTop(
|
||||||
"com.google.android.apps.nexuslauncher/.NexusLauncherActivity")
|
"com.android.launcher3/.Launcher")
|
||||||
.then()
|
.then()
|
||||||
.showsAppWindowOnTop(testApp.getPackage())
|
.showsAppWindowOnTop(mTestApp.getPackage())
|
||||||
.forAllEntries());
|
.forAllEntries());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FlakyTest(bugId = 141235985)
|
||||||
|
@Ignore("Waiting bug feedback")
|
||||||
public void checkCoveredRegion_noUncoveredRegions() {
|
public void checkCoveredRegion_noUncoveredRegions() {
|
||||||
checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
|
checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
|
||||||
getDisplayBounds()).forAllEntries());
|
getDisplayBounds()).forAllEntries());
|
||||||
@@ -98,9 +105,9 @@ public class OpenAppColdTest extends FlickerTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void checkVisibility_wallpaperLayerBecomesInvisible() {
|
public void checkVisibility_wallpaperLayerBecomesInvisible() {
|
||||||
checkResults(result -> LayersTraceSubject.assertThat(result)
|
checkResults(result -> LayersTraceSubject.assertThat(result)
|
||||||
.showsLayer("wallpaper")
|
.showsLayer("Wallpaper")
|
||||||
.then()
|
.then()
|
||||||
.hidesLayer("wallpaper")
|
.hidesLayer("Wallpaper")
|
||||||
.forAllEntries());
|
.forAllEntries());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ import androidx.test.filters.LargeTest;
|
|||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test open app to split screen.
|
* Test open app to split screen.
|
||||||
@@ -33,16 +35,17 @@ import org.junit.runner.RunWith;
|
|||||||
*/
|
*/
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class OpenAppToSplitScreenTest extends FlickerTestBase {
|
public class OpenAppToSplitScreenTest extends FlickerTestBase {
|
||||||
|
|
||||||
public OpenAppToSplitScreenTest() {
|
public OpenAppToSplitScreenTest() {
|
||||||
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void runTransition() {
|
public void runTransition() {
|
||||||
super.runTransition(appToSplitScreen(testApp, uiDevice).includeJankyRuns().build());
|
super.runTransition(appToSplitScreen(mTestApp, mUiDevice).includeJankyRuns().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -21,12 +21,16 @@ import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
|
|||||||
import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
|
import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
import androidx.test.filters.FlakyTest;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test warm launch app.
|
* Test warm launch app.
|
||||||
@@ -34,16 +38,17 @@ import org.junit.runner.RunWith;
|
|||||||
*/
|
*/
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class OpenAppWarmTest extends FlickerTestBase {
|
public class OpenAppWarmTest extends FlickerTestBase {
|
||||||
|
|
||||||
public OpenAppWarmTest() {
|
public OpenAppWarmTest() {
|
||||||
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void runTransition() {
|
public void runTransition() {
|
||||||
super.runTransition(openAppWarm(testApp, uiDevice).build());
|
super.runTransition(openAppWarm(mTestApp, mUiDevice).includeJankyRuns().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -61,9 +66,9 @@ public class OpenAppWarmTest extends FlickerTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void checkVisibility_wallpaperBecomesInvisible() {
|
public void checkVisibility_wallpaperBecomesInvisible() {
|
||||||
checkResults(result -> assertThat(result)
|
checkResults(result -> assertThat(result)
|
||||||
.showsBelowAppWindow("wallpaper")
|
.showsBelowAppWindow("Wallpaper")
|
||||||
.then()
|
.then()
|
||||||
.hidesBelowAppWindow("wallpaper")
|
.hidesBelowAppWindow("Wallpaper")
|
||||||
.forAllEntries());
|
.forAllEntries());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,12 +76,14 @@ public class OpenAppWarmTest extends FlickerTestBase {
|
|||||||
public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
|
public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
|
||||||
checkResults(result -> assertThat(result)
|
checkResults(result -> assertThat(result)
|
||||||
.showsAppWindowOnTop(
|
.showsAppWindowOnTop(
|
||||||
"com.google.android.apps.nexuslauncher/.NexusLauncherActivity")
|
"com.android.launcher3/.Launcher")
|
||||||
.then()
|
.then()
|
||||||
.showsAppWindowOnTop(testApp.getPackage())
|
.showsAppWindowOnTop(mTestApp.getPackage())
|
||||||
.forAllEntries());
|
.forAllEntries());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FlakyTest(bugId = 141235985)
|
||||||
|
@Ignore("Waiting bug feedback")
|
||||||
@Test
|
@Test
|
||||||
public void checkCoveredRegion_noUncoveredRegions() {
|
public void checkCoveredRegion_noUncoveredRegions() {
|
||||||
checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
|
checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
|
||||||
@@ -98,9 +105,9 @@ public class OpenAppWarmTest extends FlickerTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void checkVisibility_wallpaperLayerBecomesInvisible() {
|
public void checkVisibility_wallpaperLayerBecomesInvisible() {
|
||||||
checkResults(result -> LayersTraceSubject.assertThat(result)
|
checkResults(result -> LayersTraceSubject.assertThat(result)
|
||||||
.showsLayer("wallpaper")
|
.showsLayer("Wallpaper")
|
||||||
.then()
|
.then()
|
||||||
.hidesLayer("wallpaper")
|
.hidesLayer("Wallpaper")
|
||||||
.forAllEntries());
|
.forAllEntries());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,10 @@ import androidx.test.filters.LargeTest;
|
|||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test IME window opening transitions.
|
* Test IME window opening transitions.
|
||||||
@@ -32,13 +34,14 @@ import org.junit.runner.RunWith;
|
|||||||
*/
|
*/
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class OpenImeWindowTest extends FlickerTestBase {
|
public class OpenImeWindowTest extends FlickerTestBase {
|
||||||
|
|
||||||
private static final String IME_WINDOW_TITLE = "InputMethod";
|
private static final String IME_WINDOW_TITLE = "InputMethod";
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void runTransition() {
|
public void runTransition() {
|
||||||
super.runTransition(editTextSetFocus(uiDevice)
|
super.runTransition(editTextSetFocus(mUiDevice)
|
||||||
.includeJankyRuns().build());
|
.includeJankyRuns().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,12 +28,16 @@ import android.platform.helpers.IAppHelper;
|
|||||||
import android.util.Rational;
|
import android.util.Rational;
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
import androidx.test.filters.FlakyTest;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test split screen resizing window transitions.
|
* Test split screen resizing window transitions.
|
||||||
@@ -41,10 +45,13 @@ import org.junit.runner.RunWith;
|
|||||||
*/
|
*/
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
@FlakyTest(bugId = 140856143)
|
||||||
|
@Ignore("Waiting bug feedback")
|
||||||
public class ResizeSplitScreenTest extends FlickerTestBase {
|
public class ResizeSplitScreenTest extends FlickerTestBase {
|
||||||
|
|
||||||
public ResizeSplitScreenTest() {
|
public ResizeSplitScreenTest() {
|
||||||
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +60,7 @@ public class ResizeSplitScreenTest extends FlickerTestBase {
|
|||||||
IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry
|
IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry
|
||||||
.getInstrumentation(),
|
.getInstrumentation(),
|
||||||
"com.android.server.wm.flicker.testapp", "ImeApp");
|
"com.android.server.wm.flicker.testapp", "ImeApp");
|
||||||
super.runTransition(resizeSplitScreen(testApp, bottomApp, uiDevice, new Rational(1, 3),
|
super.runTransition(resizeSplitScreen(mTestApp, bottomApp, mUiDevice, new Rational(1, 3),
|
||||||
new Rational(2, 3)).includeJankyRuns().build());
|
new Rational(2, 3)).includeJankyRuns().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ import androidx.test.InstrumentationRegistry;
|
|||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
import org.junit.runners.Parameterized;
|
import org.junit.runners.Parameterized;
|
||||||
import org.junit.runners.Parameterized.Parameters;
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
@@ -47,6 +49,7 @@ import java.util.Collection;
|
|||||||
*/
|
*/
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class SeamlessAppRotationTest extends FlickerTestBase {
|
public class SeamlessAppRotationTest extends FlickerTestBase {
|
||||||
private int mBeginRotation;
|
private int mBeginRotation;
|
||||||
private int mEndRotation;
|
private int mEndRotation;
|
||||||
@@ -105,7 +108,7 @@ public class SeamlessAppRotationTest extends FlickerTestBase {
|
|||||||
|
|
||||||
super.runTransition(
|
super.runTransition(
|
||||||
changeAppRotation(mIntent, intentId, InstrumentationRegistry.getContext(),
|
changeAppRotation(mIntent, intentId, InstrumentationRegistry.getContext(),
|
||||||
uiDevice, mBeginRotation, mEndRotation).repeat(5).build());
|
mUiDevice, mBeginRotation, mEndRotation).repeat(5).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -25,8 +25,11 @@ import androidx.test.filters.LargeTest;
|
|||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test open app to split screen.
|
* Test open app to split screen.
|
||||||
@@ -34,16 +37,19 @@ import org.junit.runner.RunWith;
|
|||||||
*/
|
*/
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
@FlakyTest(bugId = 140856143)
|
||||||
|
@Ignore("Waiting bug feedback")
|
||||||
public class SplitScreenToLauncherTest extends FlickerTestBase {
|
public class SplitScreenToLauncherTest extends FlickerTestBase {
|
||||||
|
|
||||||
public SplitScreenToLauncherTest() {
|
public SplitScreenToLauncherTest() {
|
||||||
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void runTransition() {
|
public void runTransition() {
|
||||||
super.runTransition(splitScreenToLauncher(testApp, uiDevice).includeJankyRuns().build());
|
super.runTransition(splitScreenToLauncher(mTestApp, mUiDevice).includeJankyRuns().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -62,13 +68,12 @@ public class SplitScreenToLauncherTest extends FlickerTestBase {
|
|||||||
.forAllEntries());
|
.forAllEntries());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FlakyTest(bugId = 79686616)
|
|
||||||
@Test
|
@Test
|
||||||
public void checkVisibility_appLayerBecomesInVisible() {
|
public void checkVisibility_appLayerBecomesInVisible() {
|
||||||
checkResults(result -> LayersTraceSubject.assertThat(result)
|
checkResults(result -> LayersTraceSubject.assertThat(result)
|
||||||
.showsLayer(testApp.getPackage())
|
.showsLayer(mTestApp.getPackage())
|
||||||
.then()
|
.then()
|
||||||
.hidesLayer(testApp.getPackage())
|
.hidesLayer(mTestApp.getPackage())
|
||||||
.forAllEntries());
|
.forAllEntries());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,59 +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.server.wm.flicker;
|
|
||||||
|
|
||||||
import android.app.Instrumentation;
|
|
||||||
import android.platform.helpers.AbstractStandardAppHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class to take advantage of {@code IAppHelper} interface so the same test can be run against
|
|
||||||
* first party and third party apps.
|
|
||||||
*/
|
|
||||||
public class StandardAppHelper extends AbstractStandardAppHelper {
|
|
||||||
private final String mPackageName;
|
|
||||||
private final String mLauncherName;
|
|
||||||
|
|
||||||
public StandardAppHelper(Instrumentation instr, String packageName, String launcherName) {
|
|
||||||
super(instr);
|
|
||||||
mPackageName = packageName;
|
|
||||||
mLauncherName = launcherName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getPackage() {
|
|
||||||
return mPackageName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getLauncherName() {
|
|
||||||
return mLauncherName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void dismissInitialDialogs() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
package com.android.server.wm.flicker.testapp;
|
package com.android.server.wm.flicker.testapp;
|
||||||
|
|
||||||
import static android.os.SystemClock.sleep;
|
import static android.os.SystemClock.sleep;
|
||||||
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
|
||||||
|
|
||||||
import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD;
|
import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD;
|
||||||
|
|
||||||
@@ -39,8 +38,8 @@ public class SeamlessRotationActivity extends Activity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
enableSeamlessRotation();
|
enableSeamlessRotation();
|
||||||
setContentView(R.layout.activity_simple);
|
setContentView(R.layout.activity_simple);
|
||||||
boolean starveUiThread = getIntent().getExtras() != null &&
|
boolean starveUiThread = getIntent().getExtras() != null
|
||||||
getIntent().getExtras().getBoolean(EXTRA_STARVE_UI_THREAD);
|
&& getIntent().getExtras().getBoolean(EXTRA_STARVE_UI_THREAD);
|
||||||
if (starveUiThread) {
|
if (starveUiThread) {
|
||||||
starveUiThread();
|
starveUiThread();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user