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:
Jorim Jaggi
2019-09-25 13:03:18 +00:00
committed by Gerrit Code Review
54 changed files with 140 additions and 4168 deletions

View File

@@ -23,6 +23,8 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Capture screen contents -->
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
<!-- Enable / Disable tracing !-->
<uses-permission android:name="android.permission.DUMP" />
<!-- Run layers trace -->
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
<application>
@@ -33,4 +35,4 @@
android:targetPackage="com.android.server.wm.flicker"
android:label="WindowManager Flicker Tests">
</instrumentation>
</manifest>
</manifest>

View File

@@ -25,5 +25,6 @@
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/sdcard/flicker" />
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="false" />
</metrics_collector>
</configuration>

View File

@@ -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",
],
}

View File

@@ -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();
}
}
}

View File

@@ -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,
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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)");
}
}

View File

@@ -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");
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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",
],
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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", "");
}
}
}

View File

@@ -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);
};
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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() {
}
}
}

View File

@@ -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");
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -29,11 +29,15 @@ import android.util.Log;
import android.view.Surface;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@@ -44,18 +48,19 @@ import java.util.Collection;
* Cycle through supported app rotations.
* To run this test: {@code atest FlickerTest:ChangeAppRotationTest}
*/
@RunWith(Parameterized.class)
@LargeTest
@RunWith(Parameterized.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ChangeAppRotationTest extends FlickerTestBase {
private int beginRotation;
private int endRotation;
private int mBeginRotation;
private int mEndRotation;
public ChangeAppRotationTest(String beginRotationName, String endRotationName,
int beginRotation, int endRotation) {
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
"com.android.server.wm.flicker.testapp", "SimpleApp");
this.beginRotation = beginRotation;
this.endRotation = endRotation;
this.mBeginRotation = beginRotation;
this.mEndRotation = endRotation;
}
@Parameters(name = "{0}-{1}")
@@ -77,15 +82,19 @@ public class ChangeAppRotationTest extends FlickerTestBase {
@Before
public void runTransition() {
super.runTransition(
changeAppRotation(testApp, uiDevice, beginRotation, endRotation).build());
changeAppRotation(mTestApp, mUiDevice, mBeginRotation, mEndRotation).build());
}
@FlakyTest(bugId = 140855415)
@Ignore("Waiting bug feedback")
@Test
public void checkVisibility_navBarWindowIsAlwaysVisible() {
checkResults(result -> assertThat(result)
.showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
}
@FlakyTest(bugId = 140855415)
@Ignore("Waiting bug feedback")
@Test
public void checkVisibility_statusBarWindowIsAlwaysVisible() {
checkResults(result -> assertThat(result)
@@ -94,8 +103,8 @@ public class ChangeAppRotationTest extends FlickerTestBase {
@Test
public void checkPosition_navBarLayerRotatesAndScales() {
Rect startingPos = getNavigationBarPosition(beginRotation);
Rect endingPos = getNavigationBarPosition(endRotation);
Rect startingPos = getNavigationBarPosition(mBeginRotation);
Rect endingPos = getNavigationBarPosition(mEndRotation);
checkResults(result -> {
LayersTraceSubject.assertThat(result)
.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
@@ -108,22 +117,22 @@ public class ChangeAppRotationTest extends FlickerTestBase {
@Test
public void checkPosition_appLayerRotates() {
Rect startingPos = getAppPosition(beginRotation);
Rect endingPos = getAppPosition(endRotation);
Rect startingPos = getAppPosition(mBeginRotation);
Rect endingPos = getAppPosition(mEndRotation);
Log.e(TAG, "startingPos=" + startingPos + " endingPos=" + endingPos);
checkResults(result -> {
LayersTraceSubject.assertThat(result)
.hasVisibleRegion(testApp.getPackage(), startingPos).inTheBeginning();
.hasVisibleRegion(mTestApp.getPackage(), startingPos).inTheBeginning();
LayersTraceSubject.assertThat(result)
.hasVisibleRegion(testApp.getPackage(), endingPos).atTheEnd();
.hasVisibleRegion(mTestApp.getPackage(), endingPos).atTheEnd();
}
);
}
@Test
public void checkPosition_statusBarLayerScales() {
Rect startingPos = getStatusBarPosition(beginRotation);
Rect endingPos = getStatusBarPosition(endRotation);
Rect startingPos = getStatusBarPosition(mBeginRotation);
Rect endingPos = getStatusBarPosition(mEndRotation);
checkResults(result -> {
LayersTraceSubject.assertThat(result)
.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
@@ -134,12 +143,16 @@ public class ChangeAppRotationTest extends FlickerTestBase {
);
}
@FlakyTest(bugId = 140855415)
@Ignore("Waiting bug feedback")
@Test
public void checkVisibility_navBarLayerIsAlwaysVisible() {
checkResults(result -> LayersTraceSubject.assertThat(result)
.showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
}
@FlakyTest(bugId = 140855415)
@Ignore("Waiting bug feedback")
@Test
public void checkVisibility_statusBarLayerIsAlwaysVisible() {
checkResults(result -> LayersTraceSubject.assertThat(result)

View File

@@ -26,8 +26,10 @@ import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
/**
* Test IME window closing back to app window transitions.
@@ -35,6 +37,7 @@ import org.junit.runner.RunWith;
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class CloseImeWindowToAppTest extends FlickerTestBase {
private static final String IME_WINDOW_TITLE = "InputMethod";
@@ -44,7 +47,7 @@ public class CloseImeWindowToAppTest extends FlickerTestBase {
@Before
public void runTransition() {
super.runTransition(editTextLoseFocusToApp(uiDevice)
super.runTransition(editTextLoseFocusToApp(mUiDevice)
.includeJankyRuns().build());
}

View File

@@ -26,8 +26,10 @@ import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
/**
* Test IME window closing to home transitions.
@@ -35,6 +37,7 @@ import org.junit.runner.RunWith;
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class CloseImeWindowToHomeTest extends FlickerTestBase {
private static final String IME_WINDOW_TITLE = "InputMethod";
@@ -44,7 +47,7 @@ public class CloseImeWindowToHomeTest extends FlickerTestBase {
@Before
public void runTransition() {
super.runTransition(editTextLoseFocusToHome(uiDevice)
super.runTransition(editTextLoseFocusToHome(mUiDevice)
.includeJankyRuns().build());
}

View File

@@ -67,7 +67,7 @@ class CommonTransitions {
device.setOrientationNatural();
}
// Wait for animation to complete
sleep(3000);
sleep(1000);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -216,10 +216,10 @@ class CommonTransitions {
static TransitionBuilder resizeSplitScreen(IAppHelper testAppTop, IAppHelper testAppBottom,
UiDevice device, Rational startRatio, Rational stopRatio) {
String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_" +
testAppBottom.getLauncherName() + "_" +
startRatio.toString().replace("/", ":") + "_to_" +
stopRatio.toString().replace("/", ":");
String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_"
+ testAppBottom.getLauncherName() + "_"
+ startRatio.toString().replace("/", ":") + "_to_"
+ stopRatio.toString().replace("/", ":");
return TransitionRunner.newBuilder()
.withTag(testTag)
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
@@ -231,7 +231,7 @@ class CommonTransitions {
.runBefore(() -> launchSplitScreen(device))
.runBefore(() -> {
UiObject2 snapshot = device.findObject(
By.res("com.google.android.apps.nexuslauncher", "snapshot"));
By.res(device.getLauncherPackageName(), "snapshot"));
snapshot.click();
})
.runBefore(() -> AutomationUtils.resizeSplitScreen(device, startRatio))
@@ -316,4 +316,4 @@ class CommonTransitions {
.runAfterAll(testApp::exit)
.repeat(ITERATIONS);
}
}
}

View File

@@ -22,17 +22,22 @@ import android.util.Rational;
import android.view.Surface;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
/**
* Tests to help debug individual transitions, capture video recordings and create test cases.
*/
@LargeTest
@Ignore("Used for debugging transitions used in FlickerTests.")
@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class DebugTest {
private IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
"com.android.server.wm.flicker.testapp", "SimpleApp");

View File

@@ -16,20 +16,23 @@
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.google.common.truth.Truth.assertWithMessage;
import android.os.Bundle;
import android.platform.helpers.IAppHelper;
import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.UiDevice;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import java.util.HashMap;
import java.util.List;
@@ -51,10 +54,16 @@ public class FlickerTestBase {
static final String DOCKED_STACK_DIVIDER = "DockedStackDivider";
private static HashMap<String, List<TransitionResult>> transitionResults =
new HashMap<>();
IAppHelper testApp;
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
private List<TransitionResult> results;
private TransitionResult lastResult = null;
IAppHelper mTestApp;
UiDevice mUiDevice;
private List<TransitionResult> mResults;
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.
@@ -91,14 +100,14 @@ public class FlickerTestBase {
*/
void runTransition(TransitionRunner transition) {
if (transitionResults.containsKey(transition.getTestTag())) {
results = transitionResults.get(transition.getTestTag());
mResults = transitionResults.get(transition.getTestTag());
return;
}
results = transition.run().getResults();
mResults = transition.run().getResults();
/* Fail if we don't have any results due to jank */
assertWithMessage("No results to test because all transition runs were invalid because "
+ "of Jank").that(results).isNotEmpty();
transitionResults.put(transition.getTestTag(), results);
+ "of Jank").that(mResults).isNotEmpty();
transitionResults.put(transition.getTestTag(), mResults);
}
/**
@@ -106,11 +115,11 @@ public class FlickerTestBase {
*/
void checkResults(Consumer<TransitionResult> assertion) {
for (TransitionResult result : results) {
lastResult = result;
for (TransitionResult result : mResults) {
mLastResult = result;
assertion.accept(result);
}
lastResult = null;
mLastResult = null;
}
/**
@@ -119,8 +128,8 @@ public class FlickerTestBase {
*/
@After
public void markArtifactsForSaving() {
if (lastResult != null) {
lastResult.flagForSaving();
if (mLastResult != null) {
mLastResult.flagForSaving();
}
}
}

View File

@@ -21,12 +21,16 @@ import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
/**
* Test cold launch app from launcher.
@@ -34,16 +38,17 @@ import org.junit.runner.RunWith;
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OpenAppColdTest extends FlickerTestBase {
public OpenAppColdTest() {
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
"com.android.server.wm.flicker.testapp", "SimpleApp");
}
@Before
public void runTransition() {
super.runTransition(getOpenAppCold(testApp, uiDevice).build());
super.runTransition(getOpenAppCold(mTestApp, mUiDevice).build());
}
@Test
@@ -61,9 +66,9 @@ public class OpenAppColdTest extends FlickerTestBase {
@Test
public void checkVisibility_wallpaperWindowBecomesInvisible() {
checkResults(result -> assertThat(result)
.showsBelowAppWindow("wallpaper")
.showsBelowAppWindow("Wallpaper")
.then()
.hidesBelowAppWindow("wallpaper")
.hidesBelowAppWindow("Wallpaper")
.forAllEntries());
}
@@ -71,13 +76,15 @@ public class OpenAppColdTest extends FlickerTestBase {
public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
checkResults(result -> assertThat(result)
.showsAppWindowOnTop(
"com.google.android.apps.nexuslauncher/.NexusLauncherActivity")
"com.android.launcher3/.Launcher")
.then()
.showsAppWindowOnTop(testApp.getPackage())
.showsAppWindowOnTop(mTestApp.getPackage())
.forAllEntries());
}
@Test
@FlakyTest(bugId = 141235985)
@Ignore("Waiting bug feedback")
public void checkCoveredRegion_noUncoveredRegions() {
checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
getDisplayBounds()).forAllEntries());
@@ -98,9 +105,9 @@ public class OpenAppColdTest extends FlickerTestBase {
@Test
public void checkVisibility_wallpaperLayerBecomesInvisible() {
checkResults(result -> LayersTraceSubject.assertThat(result)
.showsLayer("wallpaper")
.showsLayer("Wallpaper")
.then()
.hidesLayer("wallpaper")
.hidesLayer("Wallpaper")
.forAllEntries());
}
}

View File

@@ -24,8 +24,10 @@ import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
/**
* Test open app to split screen.
@@ -33,16 +35,17 @@ import org.junit.runner.RunWith;
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OpenAppToSplitScreenTest extends FlickerTestBase {
public OpenAppToSplitScreenTest() {
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
"com.android.server.wm.flicker.testapp", "SimpleApp");
}
@Before
public void runTransition() {
super.runTransition(appToSplitScreen(testApp, uiDevice).includeJankyRuns().build());
super.runTransition(appToSplitScreen(mTestApp, mUiDevice).includeJankyRuns().build());
}
@Test

View File

@@ -21,12 +21,16 @@ import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
/**
* Test warm launch app.
@@ -34,16 +38,17 @@ import org.junit.runner.RunWith;
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OpenAppWarmTest extends FlickerTestBase {
public OpenAppWarmTest() {
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
"com.android.server.wm.flicker.testapp", "SimpleApp");
}
@Before
public void runTransition() {
super.runTransition(openAppWarm(testApp, uiDevice).build());
super.runTransition(openAppWarm(mTestApp, mUiDevice).includeJankyRuns().build());
}
@Test
@@ -61,9 +66,9 @@ public class OpenAppWarmTest extends FlickerTestBase {
@Test
public void checkVisibility_wallpaperBecomesInvisible() {
checkResults(result -> assertThat(result)
.showsBelowAppWindow("wallpaper")
.showsBelowAppWindow("Wallpaper")
.then()
.hidesBelowAppWindow("wallpaper")
.hidesBelowAppWindow("Wallpaper")
.forAllEntries());
}
@@ -71,12 +76,14 @@ public class OpenAppWarmTest extends FlickerTestBase {
public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
checkResults(result -> assertThat(result)
.showsAppWindowOnTop(
"com.google.android.apps.nexuslauncher/.NexusLauncherActivity")
"com.android.launcher3/.Launcher")
.then()
.showsAppWindowOnTop(testApp.getPackage())
.showsAppWindowOnTop(mTestApp.getPackage())
.forAllEntries());
}
@FlakyTest(bugId = 141235985)
@Ignore("Waiting bug feedback")
@Test
public void checkCoveredRegion_noUncoveredRegions() {
checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
@@ -98,9 +105,9 @@ public class OpenAppWarmTest extends FlickerTestBase {
@Test
public void checkVisibility_wallpaperLayerBecomesInvisible() {
checkResults(result -> LayersTraceSubject.assertThat(result)
.showsLayer("wallpaper")
.showsLayer("Wallpaper")
.then()
.hidesLayer("wallpaper")
.hidesLayer("Wallpaper")
.forAllEntries());
}
}

View File

@@ -23,8 +23,10 @@ import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
/**
* Test IME window opening transitions.
@@ -32,13 +34,14 @@ import org.junit.runner.RunWith;
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OpenImeWindowTest extends FlickerTestBase {
private static final String IME_WINDOW_TITLE = "InputMethod";
@Before
public void runTransition() {
super.runTransition(editTextSetFocus(uiDevice)
super.runTransition(editTextSetFocus(mUiDevice)
.includeJankyRuns().build());
}

View File

@@ -28,12 +28,16 @@ import android.platform.helpers.IAppHelper;
import android.util.Rational;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
/**
* Test split screen resizing window transitions.
@@ -41,10 +45,13 @@ import org.junit.runner.RunWith;
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 140856143)
@Ignore("Waiting bug feedback")
public class ResizeSplitScreenTest extends FlickerTestBase {
public ResizeSplitScreenTest() {
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
"com.android.server.wm.flicker.testapp", "SimpleApp");
}
@@ -53,7 +60,7 @@ public class ResizeSplitScreenTest extends FlickerTestBase {
IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry
.getInstrumentation(),
"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());
}

View File

@@ -33,8 +33,10 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@@ -47,6 +49,7 @@ import java.util.Collection;
*/
@LargeTest
@RunWith(Parameterized.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SeamlessAppRotationTest extends FlickerTestBase {
private int mBeginRotation;
private int mEndRotation;
@@ -105,7 +108,7 @@ public class SeamlessAppRotationTest extends FlickerTestBase {
super.runTransition(
changeAppRotation(mIntent, intentId, InstrumentationRegistry.getContext(),
uiDevice, mBeginRotation, mEndRotation).repeat(5).build());
mUiDevice, mBeginRotation, mEndRotation).repeat(5).build());
}
@Test

View File

@@ -25,8 +25,11 @@ import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
/**
* Test open app to split screen.
@@ -34,16 +37,19 @@ import org.junit.runner.RunWith;
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 140856143)
@Ignore("Waiting bug feedback")
public class SplitScreenToLauncherTest extends FlickerTestBase {
public SplitScreenToLauncherTest() {
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
"com.android.server.wm.flicker.testapp", "SimpleApp");
}
@Before
public void runTransition() {
super.runTransition(splitScreenToLauncher(testApp, uiDevice).includeJankyRuns().build());
super.runTransition(splitScreenToLauncher(mTestApp, mUiDevice).includeJankyRuns().build());
}
@Test
@@ -62,13 +68,12 @@ public class SplitScreenToLauncherTest extends FlickerTestBase {
.forAllEntries());
}
@FlakyTest(bugId = 79686616)
@Test
public void checkVisibility_appLayerBecomesInVisible() {
checkResults(result -> LayersTraceSubject.assertThat(result)
.showsLayer(testApp.getPackage())
.showsLayer(mTestApp.getPackage())
.then()
.hidesLayer(testApp.getPackage())
.hidesLayer(mTestApp.getPackage())
.forAllEntries());
}

View File

@@ -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() {
}
}

View File

@@ -17,7 +17,6 @@
package com.android.server.wm.flicker.testapp;
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;
@@ -39,8 +38,8 @@ public class SeamlessRotationActivity extends Activity {
super.onCreate(savedInstanceState);
enableSeamlessRotation();
setContentView(R.layout.activity_simple);
boolean starveUiThread = getIntent().getExtras() != null &&
getIntent().getExtras().getBoolean(EXTRA_STARVE_UI_THREAD);
boolean starveUiThread = getIntent().getExtras() != null
&& getIntent().getExtras().getBoolean(EXTRA_STARVE_UI_THREAD);
if (starveUiThread) {
starveUiThread();
}