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

Commit faa7aa52 authored by Yan Wang's avatar Yan Wang
Browse files

startop: Add a validator to check the correctness of event sequence.

Change-Id: Ic764cfe4f3f98e14e756897d6dcdc8c167d8a728
parent faf22d2c
Loading
Loading
Loading
Loading
+255 −0
Original line number Diff line number Diff line
/*
 * 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;

/**
 * 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) {
      Log.e(TAG, "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) {
      Log.e(TAG,
          String.format("Cannot transition from %s to %s", state, State.INTENT_STARTED));
      incAccIntentStartedEvents();
      incAccIntentStartedEvents();
      return;
    }

    Log.i(TAG, String.format("Tansition from %s to %s", state, State.INTENT_STARTED));
    state = State.INTENT_STARTED;
  }

  @Override
  public void onIntentFailed() {
    if (state == State.UNKNOWN) {
      Log.e(TAG, "IntentFailed during UNKNOWN.");
      decAccIntentStartedEvents();
      return;
    }
    if (state != State.INTENT_STARTED) {
      Log.e(TAG,
          String.format("Cannot transition from %s to %s", state, State.INTENT_FAILED));
      incAccIntentStartedEvents();
      return;
    }

    Log.i(TAG, String.format("Tansition 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) {
      Log.e(TAG, "onActivityLaunched during UNKNOWN.");
      return;
    }
    if (state != State.INTENT_STARTED) {
      Log.e(TAG,
          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) {
      Log.e(TAG, "onActivityLaunchCancelled during UNKNOWN.");
      decAccIntentStartedEvents();
      return;
    }
    if (state != State.ACTIVITY_LAUNCHED) {
      Log.e(TAG,
          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) {
      Log.e(TAG, "onActivityLaunchFinished during UNKNOWN.");
      decAccIntentStartedEvents();
      return;
    }

    if (state != State.ACTIVITY_LAUNCHED) {
      Log.e(TAG,
          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) {
      Log.e(TAG, "onReportFullyDrawn during UNKNOWN.");
      return;
    }
    if (state == State.INIT) {
      return;
    }

    if (state != State.ACTIVITY_FINISHED) {
      Log.e(TAG,
          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(
          String.format("The number of unknows 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(
          String.format("The number of unknows cannot be negative"));
    }
    if(accIntentStartedEvents == 1) {
      state = State.INIT;
    }
    --accIntentStartedEvents;
    Log.i(TAG,
        String.format("dec AccIntentStartedEvents to %d", accIntentStartedEvents));
  }
}
+2 −0
Original line number Diff line number Diff line
@@ -283,6 +283,7 @@ public class IorapForwardingService extends SystemService {
    }

    private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
    private final EventSequenceValidator mEventSequenceValidator = new EventSequenceValidator();
    private boolean mRegisteredListeners = false;

    private void registerInProcessListenersLocked() {
@@ -303,6 +304,7 @@ public class IorapForwardingService extends SystemService {
        ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
                provideLaunchObserverRegistry();
        launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
        launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator);

        mRegisteredListeners = true;
    }