Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4fb415dc authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "BouncyBall: Have app log/exit if it loses foreground" into main

parents 5054d106 cb030576
Loading
Loading
Loading
Loading

tests/BouncyBall/README

deleted100644 → 0
+0 −20
Original line number Diff line number Diff line
This is a simple graphics app which draws a ball bouncing around a screen.

This app is intended for use in automated testing (for example, to make sure
no frames are dropped while running).  It can also be run manually, but there's
a fundamental assumption that this is the only foreground app running on the
device while testing.


From the top of tree, in a shell that has been set up for building, this app
can be built with:

$ mmma -j frameworks/base/tests/BouncyBall

And then installed with:

$ adb install ${ANDROID_PRODUCT_OUT}/system/app/BouncyBallTest/BouncyBallTest.apk

This can be launched via automation with:

$ adb shell am start com.prefabulated.bouncyball/com.prefabulated.bouncyball.BouncyBallActivity
+68 −0
Original line number Diff line number Diff line
# BouncyBall test app

This is a simple graphics app which draws a ball bouncing around the screen.

This app's primary use is in automated testing, to make sure no frames are
dropped while running.

The graphics tested here are quite simple.  This app is not just intended to
assure very basic graphics work, but that the system does not drop frames as
CPUs/GPU get turned off and downclocked while in the steady state.

See https://source.android.com/docs/core/tests/debug/eval_perf#touchlatency
for more details.

## Manual usage basics

This app can be used outside of automation to check and debug this behavior.

This app fundamentally assumes that it is the only foreground app running on
the device while testing.  If that assumption is broken, this app will log
to logcat, at the "E"rror level, noting an "ASSUMPTION FAILURE".

This app will log, at the "E"rror level, each time a frame is dropped.

On a properly set up device, it is expected that this app never drops a frame.

### Helpful "flags" to flip

The source code (in
`app/src/main/java/com/prefabulated/bouncyball/BouncyBallActivity.java`) has a
few constants which can be changed to help with debugging and testing.  The
app needs to be recompiled after any of these have been changed.

* `LOG_EVERY_FRAME`  If changed to `true`, the app will log, at the "D"ebug
level, every (non-dropped) frame.
* `FORCE_DROPPED_FRAMES`  If changed to `true`, the app will drop every 64th
frame.  This can be helpful for debugging automation pipelines and confirming
app behavior.
* `ASSUMPTION_FAILURE_FORCES_EXIT`  If changed to `false`, if the app fails
the assumption that it is always in foreground focus, then the app will
keep running (even though we know the results will be wrong).


## Local build and install

From the top of tree, in a shell that has been set up for building:

```
$ mmma -j frameworks/base/tests/BouncyBall
$ adb install ${ANDROID_PRODUCT_OUT}/system/app/BouncyBallTest/BouncyBallTest.apk
```

## Launch app from command line

Assuming the app is installed on the device:

```
$ adb shell am start com.prefabulated.bouncyball/com.prefabulated.bouncyball.BouncyBallActivity
```

## Using Perfetto for analysis

While the app will self-report when it drops frames, indicating an issue, that's
not very helpful for figuring out why.

TODO(b/408044970): Document how automation uses Perfetto, and how it can be used
here.
+41 −2
Original line number Diff line number Diff line
@@ -33,11 +33,19 @@ public class BouncyBallActivity extends AppCompatActivity {
    // Since logging (to logcat) takes system resources, we chose not to log
    // data every frame by default.
    private static final boolean LOG_EVERY_FRAME = false;

    // To help with debugging and verifying behavior when frames are dropped,
    // this will drop one in every 64 frames.
    private static final boolean FORCE_DROPPED_FRAMES = false;

    // If the app fails an assumption we have for it (primarily that it is
    // the only foreground app), then its results cannot be trusted.  By
    // default, we choose to immediately exit in this situation.  Note that
    // dropping a frame is not considered an assumption failure.
    private static final boolean ASSUMPTION_FAILURE_FORCES_EXIT = true;

    private static final String LOG_TAG = "BouncyBall";

    private static final float DESIRED_FRAME_RATE = 60.0f;

    // Our focus isn't smoothness on startup; it's smoothness once we're
@@ -45,6 +53,8 @@ public class BouncyBallActivity extends AppCompatActivity {
    private static final int INITIAL_FRAMES_TO_IGNORE = 6;

    private int mDisplayId = -1;
    private boolean mHasFocus = false;
    private boolean mWarmedUp = false;
    private float mFrameRate;
    private long mFrameMaxDurationNanos;
    private int mNumFramesDropped = 0;
@@ -77,14 +87,23 @@ public class BouncyBallActivity extends AppCompatActivity {

                @Override
                public void doFrame(long frameTimeNanos) {
                    if (mFrameCount >= INITIAL_FRAMES_TO_IGNORE) {
                    if (mFrameCount == INITIAL_FRAMES_TO_IGNORE) {
                        mWarmedUp = true;
                        if (!mHasFocus) {
                            String msg = "App does not have focus after "
                                    + mFrameCount + " frames";
                            reportAssumptionFailure(msg);
                        }
                    }
                    if (mWarmedUp) {
                        long elapsedNanos = frameTimeNanos - mLastFrameTimeNanos;
                        if (elapsedNanos > mFrameMaxDurationNanos) {
                            mNumFramesDropped++;
                            Log.e(LOG_TAG, "FRAME DROPPED (total " + mNumFramesDropped
                                    + "): Took " + nanosToMillis(elapsedNanos) + "ms");
                        } else if (LOG_EVERY_FRAME) {
                            Log.d(LOG_TAG, "Frame took " + nanosToMillis(elapsedNanos) + "ms");
                            Log.d(LOG_TAG, "Frame " + mFrameCount + " took "
                                    + nanosToMillis(elapsedNanos) + "ms");
                        }
                    }
                    mLastFrameTimeNanos = frameTimeNanos;
@@ -125,6 +144,18 @@ public class BouncyBallActivity extends AppCompatActivity {
        Trace.endSection();
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);

        if (mWarmedUp) {
            // After our initial frames, this app should always be in focus.
            String state = hasFocus ? "gain" : "loss";
            reportAssumptionFailure("Unexpected " + state + " of focus");
        }
        mHasFocus = hasFocus;
    }

    // If available at our current resolution, use 60Hz.  If not, use the
    // lowest refresh rate above 60Hz which is available.  Otherwise, throw
    // an exception which kills the app.
@@ -195,4 +226,12 @@ public class BouncyBallActivity extends AppCompatActivity {
    private float nanosToMillis(long nanos) {
        return nanos / (1_000_000.0f);
    }

    private void reportAssumptionFailure(String msg) {
        Log.e(LOG_TAG, "ASSUMPTION FAILURE.  " + msg);
        if (ASSUMPTION_FAILURE_FORCES_EXIT) {
            Log.e(LOG_TAG, "Exiting app due to assumption failure.");
            System.exit(1);
        }
    }
}