Loading startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java 0 → 100644 +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)); } } startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java +2 −0 Original line number Diff line number Diff line Loading @@ -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() { Loading @@ -303,6 +304,7 @@ public class IorapForwardingService extends SystemService { ActivityMetricsLaunchObserverRegistry launchObserverRegistry = provideLaunchObserverRegistry(); launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver); launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator); mRegisteredListeners = true; } Loading Loading
startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java 0 → 100644 +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)); } }
startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java +2 −0 Original line number Diff line number Diff line Loading @@ -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() { Loading @@ -303,6 +304,7 @@ public class IorapForwardingService extends SystemService { ActivityMetricsLaunchObserverRegistry launchObserverRegistry = provideLaunchObserverRegistry(); launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver); launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator); mRegisteredListeners = true; } Loading