1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.android.startop.iorap; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Intent; 22 import android.util.Log; 23 24 import com.android.server.wm.ActivityMetricsLaunchObserver; 25 26 import java.io.StringWriter; 27 import java.io.PrintWriter; 28 29 /** 30 * A validator to check the correctness of event sequence during app startup. 31 * 32 * <p> A valid state transition of event sequence is shown as the following: 33 * 34 * <pre> 35 * 36 * +--------------------+ 37 * | | 38 * | INIT | 39 * | | 40 * +--------------------+ 41 * | 42 * | 43 * ↓ 44 * +--------------------+ 45 * | | 46 * +-------------------| INTENT_STARTED | ←--------------------------------+ 47 * | | | | 48 * | +--------------------+ | 49 * | | | 50 * | | | 51 * ↓ ↓ | 52 * +--------------------+ +--------------------+ | 53 * | | | | | 54 * | INTENT_FAILED | | ACTIVITY_LAUNCHED |------------------+ | 55 * | | | | | | 56 * +--------------------+ +--------------------+ | | 57 * | | | | 58 * | ↓ ↓ | 59 * | +--------------------+ +--------------------+ | 60 * | | | | | | 61 * +------------------ | ACTIVITY_FINISHED | | ACTIVITY_CANCELLED | | 62 * | | | | | | 63 * | +--------------------+ +--------------------+ | 64 * | | | | 65 * | | | | 66 * | ↓ | | 67 * | +--------------------+ | | 68 * | | | | | 69 * | | REPORT_FULLY_DRAWN | | | 70 * | | | | | 71 * | +--------------------+ | | 72 * | | | | 73 * | | | | 74 * | ↓ | | 75 * | +--------------------+ | | 76 * | | | | | 77 * +-----------------→ | END |←-----------------+ | 78 * | | | 79 * +--------------------+ | 80 * | | 81 * | | 82 * | | 83 * +--------------------------------------------- 84 * 85 * <p> END is not a real state in implementation. All states that points to END directly 86 * could transition to INTENT_STARTED. 87 * 88 * <p> If any bad transition happened, the state becomse UNKNOWN. The UNKNOWN state 89 * could be accumulated, because during the UNKNOWN state more IntentStarted may 90 * be triggered. To recover from UNKNOWN to INIT, all the accumualted IntentStarted 91 * should termniate. 92 * 93 * <p> During UNKNOWN state, each IntentStarted increases the accumulation, and any of 94 * IntentFailed, ActivityLaunchCancelled and ActivityFinished decreases the accumulation. 95 * ReportFullyDrawn doesn't impact the accumulation. 96 */ 97 public class EventSequenceValidator implements ActivityMetricsLaunchObserver { 98 static final String TAG = "EventSequenceValidator"; 99 /** $> adb shell 'setprop log.tag.EventSequenceValidator VERBOSE' */ 100 public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 101 private State state = State.INIT; 102 private long accIntentStartedEvents = 0; 103 104 @Override onIntentStarted(@onNull Intent intent, long timestampNs)105 public void onIntentStarted(@NonNull Intent intent, long timestampNs) { 106 if (state == State.UNKNOWN) { 107 logWarningWithStackTrace("IntentStarted during UNKNOWN. " + intent); 108 incAccIntentStartedEvents(); 109 return; 110 } 111 112 if (state != State.INIT && 113 state != State.INTENT_FAILED && 114 state != State.ACTIVITY_CANCELLED && 115 state != State.ACTIVITY_FINISHED && 116 state != State.REPORT_FULLY_DRAWN) { 117 logWarningWithStackTrace( 118 String.format("Cannot transition from %s to %s", state, State.INTENT_STARTED)); 119 incAccIntentStartedEvents(); 120 incAccIntentStartedEvents(); 121 return; 122 } 123 124 Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_STARTED)); 125 state = State.INTENT_STARTED; 126 } 127 128 @Override onIntentFailed()129 public void onIntentFailed() { 130 if (state == State.UNKNOWN) { 131 logWarningWithStackTrace("onIntentFailed during UNKNOWN."); 132 decAccIntentStartedEvents(); 133 return; 134 } 135 if (state != State.INTENT_STARTED) { 136 logWarningWithStackTrace( 137 String.format("Cannot transition from %s to %s", state, State.INTENT_FAILED)); 138 incAccIntentStartedEvents(); 139 return; 140 } 141 142 Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_FAILED)); 143 state = State.INTENT_FAILED; 144 } 145 146 @Override onActivityLaunched(@onNull @ctivityRecordProto byte[] activity, @Temperature int temperature)147 public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity, 148 @Temperature int temperature) { 149 if (state == State.UNKNOWN) { 150 logWarningWithStackTrace("onActivityLaunched during UNKNOWN."); 151 return; 152 } 153 if (state != State.INTENT_STARTED) { 154 logWarningWithStackTrace( 155 String.format("Cannot transition from %s to %s", state, State.ACTIVITY_LAUNCHED)); 156 incAccIntentStartedEvents(); 157 return; 158 } 159 160 Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_LAUNCHED)); 161 state = State.ACTIVITY_LAUNCHED; 162 } 163 164 @Override onActivityLaunchCancelled(@ullable @ctivityRecordProto byte[] activity)165 public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) { 166 if (state == State.UNKNOWN) { 167 logWarningWithStackTrace("onActivityLaunchCancelled during UNKNOWN."); 168 decAccIntentStartedEvents(); 169 return; 170 } 171 if (state != State.ACTIVITY_LAUNCHED) { 172 logWarningWithStackTrace( 173 String.format("Cannot transition from %s to %s", state, State.ACTIVITY_CANCELLED)); 174 incAccIntentStartedEvents(); 175 return; 176 } 177 178 Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_CANCELLED)); 179 state = State.ACTIVITY_CANCELLED; 180 } 181 182 @Override onActivityLaunchFinished(@onNull @ctivityRecordProto byte[] activity, long timestampNs)183 public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity, 184 long timestampNs) { 185 if (state == State.UNKNOWN) { 186 logWarningWithStackTrace("onActivityLaunchFinished during UNKNOWN."); 187 decAccIntentStartedEvents(); 188 return; 189 } 190 191 if (state != State.ACTIVITY_LAUNCHED) { 192 logWarningWithStackTrace( 193 String.format("Cannot transition from %s to %s", state, State.ACTIVITY_FINISHED)); 194 incAccIntentStartedEvents(); 195 return; 196 } 197 198 Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_FINISHED)); 199 state = State.ACTIVITY_FINISHED; 200 } 201 202 @Override onReportFullyDrawn(@onNull @ctivityRecordProto byte[] activity, long timestampNs)203 public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity, 204 long timestampNs) { 205 if (state == State.UNKNOWN) { 206 logWarningWithStackTrace("onReportFullyDrawn during UNKNOWN."); 207 return; 208 } 209 if (state == State.INIT) { 210 return; 211 } 212 213 if (state != State.ACTIVITY_FINISHED) { 214 logWarningWithStackTrace( 215 String.format("Cannot transition from %s to %s", state, State.REPORT_FULLY_DRAWN)); 216 return; 217 } 218 219 Log.i(TAG, String.format("Transition from %s to %s", state, State.REPORT_FULLY_DRAWN)); 220 state = State.REPORT_FULLY_DRAWN; 221 } 222 223 enum State { 224 INIT, 225 INTENT_STARTED, 226 INTENT_FAILED, 227 ACTIVITY_LAUNCHED, 228 ACTIVITY_CANCELLED, 229 ACTIVITY_FINISHED, 230 REPORT_FULLY_DRAWN, 231 UNKNOWN, 232 } 233 incAccIntentStartedEvents()234 private void incAccIntentStartedEvents() { 235 if (accIntentStartedEvents < 0) { 236 throw new AssertionError("The number of unknowns cannot be negative"); 237 } 238 if (accIntentStartedEvents == 0) { 239 state = State.UNKNOWN; 240 } 241 ++accIntentStartedEvents; 242 Log.i(TAG, 243 String.format("inc AccIntentStartedEvents to %d", accIntentStartedEvents)); 244 } 245 decAccIntentStartedEvents()246 private void decAccIntentStartedEvents() { 247 if (accIntentStartedEvents <= 0) { 248 throw new AssertionError("The number of unknowns cannot be negative"); 249 } 250 if(accIntentStartedEvents == 1) { 251 state = State.INIT; 252 } 253 --accIntentStartedEvents; 254 Log.i(TAG, 255 String.format("dec AccIntentStartedEvents to %d", accIntentStartedEvents)); 256 } 257 logWarningWithStackTrace(String log)258 private void logWarningWithStackTrace(String log) { 259 if (DEBUG) { 260 StringWriter sw = new StringWriter(); 261 PrintWriter pw = new PrintWriter(sw); 262 new Throwable("EventSequenceValidator#getStackTrace").printStackTrace(pw); 263 Log.wtf(TAG, String.format("%s\n%s", log, sw)); 264 } 265 } 266 } 267 268