summaryrefslogtreecommitdiff
path: root/startop/iorap
diff options
context:
space:
mode:
authorYan Wang <yawanng@google.com>2019-11-20 10:52:33 -0800
committerYan Wang <yawanng@google.com>2020-01-02 10:25:32 -0800
commitfaa7aa527ef79d754b5c144e4e1941d3cb6c2336 (patch)
treed3b511ec5e982956f76eb5ce2d7320a4109d8c0f /startop/iorap
parentfaf22d2c2ac4b5e6a4c4f8e23a19ebb4c6688b5b (diff)
startop: Add a validator to check the correctness of event sequence.
Change-Id: Ic764cfe4f3f98e14e756897d6dcdc8c167d8a728
Diffstat (limited to 'startop/iorap')
-rw-r--r--startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java255
-rw-r--r--startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java2
2 files changed, 257 insertions, 0 deletions
diff --git a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
new file mode 100644
index 000000000000..b75510b576f6
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
@@ -0,0 +1,255 @@
+/*
+ * 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));
+ }
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index f753548516ef..badff7bc2f8c 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -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;
}