blob: 6b194550cc111bf578d6d7e0e3eb332bb2428287 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.startop.iorap;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
import android.util.Log;
import com.android.server.wm.ActivityMetricsLaunchObserver;
import java.io.StringWriter;
import java.io.PrintWriter;
/**
* A validator to check the correctness of event sequence during app startup.
*
* <p> A valid state transition of event sequence is shown as the following:
*
* <pre>
*
* +--------------------+
* | |
* | INIT |
* | |
* +--------------------+
* |
* |
* ↓
* +--------------------+
* | |
* +-------------------| INTENT_STARTED | ←--------------------------------+
* | | | |
* | +--------------------+ |
* | | |
* | | |
* ↓ ↓ |
* +--------------------+ +--------------------+ |
* | | | | |
* | INTENT_FAILED | | ACTIVITY_LAUNCHED |------------------+ |
* | | | | | |
* +--------------------+ +--------------------+ | |
* | | | |
* | ↓ ↓ |
* | +--------------------+ +--------------------+ |
* | | | | | |
* +------------------ | ACTIVITY_FINISHED | | ACTIVITY_CANCELLED | |
* | | | | | |
* | +--------------------+ +--------------------+ |
* | | | |
* | | | |
* | ↓ | |
* | +--------------------+ | |
* | | | | |
* | | REPORT_FULLY_DRAWN | | |
* | | | | |
* | +--------------------+ | |
* | | | |
* | | | |
* | ↓ | |
* | +--------------------+ | |
* | | | | |
* +-----------------→ | END |←-----------------+ |
* | | |
* +--------------------+ |
* | |
* | |
* | |
* +---------------------------------------------
*
* <p> END is not a real state in implementation. All states that points to END directly
* could transition to INTENT_STARTED.
*
* <p> If any bad transition happened, the state becomse UNKNOWN. The UNKNOWN state
* could be accumulated, because during the UNKNOWN state more IntentStarted may
* be triggered. To recover from UNKNOWN to INIT, all the accumualted IntentStarted
* should termniate.
*
* <p> During UNKNOWN state, each IntentStarted increases the accumulation, and any of
* IntentFailed, ActivityLaunchCancelled and ActivityFinished decreases the accumulation.
* ReportFullyDrawn doesn't impact the accumulation.
*/
public class EventSequenceValidator implements ActivityMetricsLaunchObserver {
static final String TAG = "EventSequenceValidator";
private State state = State.INIT;
private long accIntentStartedEvents = 0;
@Override
public void onIntentStarted(@NonNull Intent intent, long timestampNs) {
if (state == State.UNKNOWN) {
logWarningWithStackTrace("IntentStarted during UNKNOWN. " + intent);
incAccIntentStartedEvents();
return;
}
if (state != State.INIT &&
state != State.INTENT_FAILED &&
state != State.ACTIVITY_CANCELLED &&
state != State.ACTIVITY_FINISHED &&
state != State.REPORT_FULLY_DRAWN) {
logWarningWithStackTrace(
String.format("Cannot transition from %s to %s", state, State.INTENT_STARTED));
incAccIntentStartedEvents();
incAccIntentStartedEvents();
return;
}
Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_STARTED));
state = State.INTENT_STARTED;
}
@Override
public void onIntentFailed() {
if (state == State.UNKNOWN) {
logWarningWithStackTrace("onIntentFailed during UNKNOWN.");
decAccIntentStartedEvents();
return;
}
if (state != State.INTENT_STARTED) {
logWarningWithStackTrace(
String.format("Cannot transition from %s to %s", state, State.INTENT_FAILED));
incAccIntentStartedEvents();
return;
}
Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_FAILED));
state = State.INTENT_FAILED;
}
@Override
public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity,
@Temperature int temperature) {
if (state == State.UNKNOWN) {
logWarningWithStackTrace("onActivityLaunched during UNKNOWN.");
return;
}
if (state != State.INTENT_STARTED) {
logWarningWithStackTrace(
String.format("Cannot transition from %s to %s", state, State.ACTIVITY_LAUNCHED));
incAccIntentStartedEvents();
return;
}
Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_LAUNCHED));
state = State.ACTIVITY_LAUNCHED;
}
@Override
public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) {
if (state == State.UNKNOWN) {
logWarningWithStackTrace("onActivityLaunchCancelled during UNKNOWN.");
decAccIntentStartedEvents();
return;
}
if (state != State.ACTIVITY_LAUNCHED) {
logWarningWithStackTrace(
String.format("Cannot transition from %s to %s", state, State.ACTIVITY_CANCELLED));
incAccIntentStartedEvents();
return;
}
Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_CANCELLED));
state = State.ACTIVITY_CANCELLED;
}
@Override
public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity,
long timestampNs) {
if (state == State.UNKNOWN) {
logWarningWithStackTrace("onActivityLaunchFinished during UNKNOWN.");
decAccIntentStartedEvents();
return;
}
if (state != State.ACTIVITY_LAUNCHED) {
logWarningWithStackTrace(
String.format("Cannot transition from %s to %s", state, State.ACTIVITY_FINISHED));
incAccIntentStartedEvents();
return;
}
Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_FINISHED));
state = State.ACTIVITY_FINISHED;
}
@Override
public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
long timestampNs) {
if (state == State.UNKNOWN) {
logWarningWithStackTrace("onReportFullyDrawn during UNKNOWN.");
return;
}
if (state == State.INIT) {
return;
}
if (state != State.ACTIVITY_FINISHED) {
logWarningWithStackTrace(
String.format("Cannot transition from %s to %s", state, State.REPORT_FULLY_DRAWN));
return;
}
Log.i(TAG, String.format("Transition from %s to %s", state, State.REPORT_FULLY_DRAWN));
state = State.REPORT_FULLY_DRAWN;
}
enum State {
INIT,
INTENT_STARTED,
INTENT_FAILED,
ACTIVITY_LAUNCHED,
ACTIVITY_CANCELLED,
ACTIVITY_FINISHED,
REPORT_FULLY_DRAWN,
UNKNOWN,
}
private void incAccIntentStartedEvents() {
if (accIntentStartedEvents < 0) {
throw new AssertionError("The number of unknowns cannot be negative");
}
if (accIntentStartedEvents == 0) {
state = State.UNKNOWN;
}
++accIntentStartedEvents;
Log.i(TAG,
String.format("inc AccIntentStartedEvents to %d", accIntentStartedEvents));
}
private void decAccIntentStartedEvents() {
if (accIntentStartedEvents <= 0) {
throw new AssertionError("The number of unknowns cannot be negative");
}
if(accIntentStartedEvents == 1) {
state = State.INIT;
}
--accIntentStartedEvents;
Log.i(TAG,
String.format("dec AccIntentStartedEvents to %d", accIntentStartedEvents));
}
private void logWarningWithStackTrace(String log) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
new Throwable("EventSequenceValidator#getStackTrace").printStackTrace(pw);
Log.w(TAG, String.format("%s\n%s", log, sw));
}
}