1 /*
2  * Copyright (C) 2018 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.LongDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.ComponentName;
23 import android.content.Intent;
24 import android.content.pm.ActivityInfo;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.util.proto.ProtoOutputStream;
28 
29 import com.android.server.wm.ActivityMetricsLaunchObserver;
30 import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
31 import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
32 
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.lang.reflect.InvocationTargetException;
36 import java.util.Arrays;
37 import java.util.Objects;
38 
39 /**
40  * Provide a hint to iorapd that an app launch sequence has transitioned state.<br /><br />
41  *
42  * Knowledge of when an activity starts/stops can be used by iorapd to increase system
43  * performance (e.g. by launching perfetto tracing to record an io profile, or by
44  * playing back an ioprofile via readahead) over the long run.<br /><br />
45  *
46  * /@see com.google.android.startop.iorap.IIorap#onAppLaunchEvent <br /><br />
47  * @see com.android.server.wm.ActivityMetricsLaunchObserver
48  *      ActivityMetricsLaunchObserver for the possible event states.
49  * @hide
50  */
51 public abstract class AppLaunchEvent implements Parcelable {
52     @LongDef
53     @Retention(RetentionPolicy.SOURCE)
54     public @interface SequenceId {}
55 
56     public final @SequenceId
57     long sequenceId;
58 
AppLaunchEvent(@equenceId long sequenceId)59     protected AppLaunchEvent(@SequenceId long sequenceId) {
60         this.sequenceId = sequenceId;
61     }
62 
63     @Override
equals(Object other)64     public boolean equals(Object other) {
65         if (other instanceof AppLaunchEvent) {
66             return equals((AppLaunchEvent) other);
67         }
68         return false;
69     }
70 
equals(AppLaunchEvent other)71     protected boolean equals(AppLaunchEvent other) {
72         return sequenceId == other.sequenceId;
73     }
74 
75 
76     @Override
toString()77     public String toString() {
78         return getClass().getSimpleName() +
79                 "{" + "sequenceId=" + Long.toString(sequenceId) +
80                 toStringBody() + "}";
81     }
82 
toStringBody()83     protected String toStringBody() { return ""; };
84 
85     // List of possible variants:
86 
87     public static final class IntentStarted extends AppLaunchEvent {
88         @NonNull
89         public final Intent intent;
90         public final long timestampNs;
91 
IntentStarted(@equenceId long sequenceId, Intent intent, long timestampNs)92         public IntentStarted(@SequenceId long sequenceId,
93                              Intent intent,
94                              long timestampNs) {
95             super(sequenceId);
96             this.intent = intent;
97             this.timestampNs = timestampNs;
98 
99             Objects.requireNonNull(intent, "intent");
100         }
101 
102         @Override
equals(Object other)103         public boolean equals(Object other) {
104             if (other instanceof IntentStarted) {
105                 return intent.equals(((IntentStarted)other).intent) &&
106                        timestampNs == ((IntentStarted)other).timestampNs &&
107                        super.equals(other);
108             }
109             return false;
110         }
111 
112         @Override
toStringBody()113         protected String toStringBody() {
114             return ", intent=" + intent.toString() +
115                    " , timestampNs=" + Long.toString(timestampNs);
116         }
117 
118 
119         @Override
writeToParcelImpl(Parcel p, int flags)120         protected void writeToParcelImpl(Parcel p, int flags) {
121             super.writeToParcelImpl(p, flags);
122             IntentProtoParcelable.write(p, intent, flags);
123             p.writeLong(timestampNs);
124         }
125 
IntentStarted(Parcel p)126         IntentStarted(Parcel p) {
127             super(p);
128             intent = IntentProtoParcelable.create(p);
129             timestampNs = p.readLong();
130         }
131     }
132 
133     public static final class IntentFailed extends AppLaunchEvent {
IntentFailed(@equenceId long sequenceId)134         public IntentFailed(@SequenceId long sequenceId) {
135             super(sequenceId);
136         }
137 
138         @Override
equals(Object other)139         public boolean equals(Object other) {
140             if (other instanceof IntentFailed) {
141                 return super.equals(other);
142             }
143             return false;
144         }
145 
IntentFailed(Parcel p)146         IntentFailed(Parcel p) {
147             super(p);
148         }
149     }
150 
151     public static abstract class BaseWithActivityRecordData extends AppLaunchEvent {
152         public final @NonNull
153         @ActivityRecordProto byte[] activityRecordSnapshot;
154 
BaseWithActivityRecordData(@equenceId long sequenceId, @NonNull @ActivityRecordProto byte[] snapshot)155         protected BaseWithActivityRecordData(@SequenceId long sequenceId,
156                 @NonNull @ActivityRecordProto byte[] snapshot) {
157             super(sequenceId);
158             activityRecordSnapshot = snapshot;
159 
160             Objects.requireNonNull(snapshot, "snapshot");
161         }
162 
163         @Override
equals(Object other)164         public boolean equals(Object other) {
165             if (other instanceof BaseWithActivityRecordData) {
166                 return (Arrays.equals(activityRecordSnapshot,
167                       ((BaseWithActivityRecordData)other).activityRecordSnapshot)) &&
168                         super.equals(other);
169             }
170             return false;
171         }
172 
173         @Override
toStringBody()174         protected String toStringBody() {
175             return ", " + new String(activityRecordSnapshot);
176         }
177 
178         @Override
writeToParcelImpl(Parcel p, int flags)179         protected void writeToParcelImpl(Parcel p, int flags) {
180            super.writeToParcelImpl(p, flags);
181            ActivityRecordProtoParcelable.write(p, activityRecordSnapshot, flags);
182         }
183 
BaseWithActivityRecordData(Parcel p)184         BaseWithActivityRecordData(Parcel p) {
185             super(p);
186             activityRecordSnapshot = ActivityRecordProtoParcelable.create(p);
187         }
188     }
189 
190     public static final class ActivityLaunched extends BaseWithActivityRecordData {
191         public final @ActivityMetricsLaunchObserver.Temperature
192         int temperature;
193 
ActivityLaunched(@equenceId long sequenceId, @NonNull @ActivityRecordProto byte[] snapshot, @ActivityMetricsLaunchObserver.Temperature int temperature)194         public ActivityLaunched(@SequenceId long sequenceId,
195                 @NonNull @ActivityRecordProto byte[] snapshot,
196                 @ActivityMetricsLaunchObserver.Temperature int temperature) {
197             super(sequenceId, snapshot);
198             this.temperature = temperature;
199         }
200 
201         @Override
equals(Object other)202         public boolean equals(Object other) {
203             if (other instanceof ActivityLaunched) {
204                 return temperature == ((ActivityLaunched)other).temperature &&
205                         super.equals(other);
206             }
207             return false;
208         }
209 
210         @Override
toStringBody()211         protected String toStringBody() {
212             return super.toStringBody() + ", temperature=" + Integer.toString(temperature);
213         }
214 
215         @Override
writeToParcelImpl(Parcel p, int flags)216         protected void writeToParcelImpl(Parcel p, int flags) {
217            super.writeToParcelImpl(p, flags);
218            p.writeInt(temperature);
219         }
220 
ActivityLaunched(Parcel p)221         ActivityLaunched(Parcel p) {
222             super(p);
223             temperature = p.readInt();
224         }
225     }
226 
227     public static final class ActivityLaunchFinished extends BaseWithActivityRecordData {
228         public final long timestampNs;
229 
ActivityLaunchFinished(@equenceId long sequenceId, @NonNull @ActivityRecordProto byte[] snapshot, long timestampNs)230         public ActivityLaunchFinished(@SequenceId long sequenceId,
231                 @NonNull @ActivityRecordProto byte[] snapshot,
232                 long timestampNs) {
233             super(sequenceId, snapshot);
234             this.timestampNs = timestampNs;
235         }
236 
237         @Override
equals(Object other)238         public boolean equals(Object other) {
239             if (other instanceof ActivityLaunchFinished) {
240                 return timestampNs == ((ActivityLaunchFinished)other).timestampNs &&
241                        super.equals(other);
242             }
243             return false;
244         }
245 
246         @Override
toStringBody()247         protected String toStringBody() {
248             return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
249         }
250 
251         @Override
writeToParcelImpl(Parcel p, int flags)252         protected void writeToParcelImpl(Parcel p, int flags) {
253            super.writeToParcelImpl(p, flags);
254            p.writeLong(timestampNs);
255         }
256 
ActivityLaunchFinished(Parcel p)257         ActivityLaunchFinished(Parcel p) {
258             super(p);
259             timestampNs = p.readLong();
260         }
261     }
262 
263      public static class ActivityLaunchCancelled extends AppLaunchEvent {
264         public final @Nullable @ActivityRecordProto byte[] activityRecordSnapshot;
265 
ActivityLaunchCancelled(@equenceId long sequenceId, @Nullable @ActivityRecordProto byte[] snapshot)266         public ActivityLaunchCancelled(@SequenceId long sequenceId,
267                 @Nullable @ActivityRecordProto byte[] snapshot) {
268             super(sequenceId);
269             activityRecordSnapshot = snapshot;
270         }
271 
272         @Override
equals(Object other)273         public boolean equals(Object other) {
274             if (other instanceof ActivityLaunchCancelled) {
275                 return Arrays.equals(activityRecordSnapshot,
276                     ((ActivityLaunchCancelled)other).activityRecordSnapshot) &&
277                         super.equals(other);
278             }
279             return false;
280         }
281 
282         @Override
toStringBody()283         protected String toStringBody() {
284             return super.toStringBody() + ", " + new String(activityRecordSnapshot);
285         }
286 
287         @Override
writeToParcelImpl(Parcel p, int flags)288         protected void writeToParcelImpl(Parcel p, int flags) {
289            super.writeToParcelImpl(p, flags);
290            if (activityRecordSnapshot != null) {
291                p.writeBoolean(true);
292                ActivityRecordProtoParcelable.write(p, activityRecordSnapshot, flags);
293            } else {
294                p.writeBoolean(false);
295            }
296         }
297 
ActivityLaunchCancelled(Parcel p)298         ActivityLaunchCancelled(Parcel p) {
299             super(p);
300             if (p.readBoolean()) {
301                 activityRecordSnapshot = ActivityRecordProtoParcelable.create(p);
302             } else {
303                 activityRecordSnapshot = null;
304             }
305         }
306     }
307 
308     public static final class ReportFullyDrawn extends BaseWithActivityRecordData {
309         public final long timestampNs;
310 
ReportFullyDrawn(@equenceId long sequenceId, @NonNull @ActivityRecordProto byte[] snapshot, long timestampNs)311         public ReportFullyDrawn(@SequenceId long sequenceId,
312                 @NonNull @ActivityRecordProto byte[] snapshot,
313                 long timestampNs) {
314             super(sequenceId, snapshot);
315             this.timestampNs = timestampNs;
316         }
317 
318         @Override
equals(Object other)319         public boolean equals(Object other) {
320             if (other instanceof ReportFullyDrawn) {
321                 return timestampNs == ((ReportFullyDrawn)other).timestampNs &&
322                         super.equals(other);
323             }
324             return false;
325         }
326 
327         @Override
toStringBody()328         protected String toStringBody() {
329             return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
330         }
331 
332         @Override
writeToParcelImpl(Parcel p, int flags)333         protected void writeToParcelImpl(Parcel p, int flags) {
334            super.writeToParcelImpl(p, flags);
335            p.writeLong(timestampNs);
336         }
337 
ReportFullyDrawn(Parcel p)338         ReportFullyDrawn(Parcel p) {
339             super(p);
340             timestampNs = p.readLong();
341         }
342     }
343 
344     @Override
describeContents()345     public @ContentsFlags int describeContents() { return 0; }
346 
347     @Override
writeToParcel(Parcel p, @WriteFlags int flags)348     public void writeToParcel(Parcel p, @WriteFlags int flags) {
349         p.writeInt(getTypeIndex());
350 
351         writeToParcelImpl(p, flags);
352     }
353 
354 
355     public static Creator<AppLaunchEvent> CREATOR =
356             new Creator<AppLaunchEvent>() {
357         @Override
358         public AppLaunchEvent createFromParcel(Parcel source) {
359             int typeIndex = source.readInt();
360 
361             Class<?> kls = getClassFromTypeIndex(typeIndex);
362             if (kls == null) {
363                 throw new IllegalArgumentException("Invalid type index: " + typeIndex);
364             }
365 
366             try {
367                 return (AppLaunchEvent) kls.getConstructor(Parcel.class).newInstance(source);
368             } catch (InstantiationException e) {
369                 throw new AssertionError(e);
370             } catch (IllegalAccessException e) {
371                 throw new AssertionError(e);
372             } catch (InvocationTargetException e) {
373                 throw new AssertionError(e);
374             } catch (NoSuchMethodException e) {
375                 throw new AssertionError(e);
376             }
377         }
378 
379         @Override
380         public AppLaunchEvent[] newArray(int size) {
381             return new AppLaunchEvent[0];
382         }
383     };
384 
writeToParcelImpl(Parcel p, int flags)385     protected void writeToParcelImpl(Parcel p, int flags) {
386         p.writeLong(sequenceId);
387     }
388 
AppLaunchEvent(Parcel p)389     protected AppLaunchEvent(Parcel p) {
390         sequenceId = p.readLong();
391     }
392 
getTypeIndex()393     private int getTypeIndex() {
394         for (int i = 0; i < sTypes.length; ++i) {
395             if (sTypes[i].equals(this.getClass())) {
396                 return i;
397             }
398         }
399         throw new AssertionError("sTypes did not include this type: " + this.getClass());
400     }
401 
getClassFromTypeIndex(int typeIndex)402     private static @Nullable Class<?> getClassFromTypeIndex(int typeIndex) {
403         if (typeIndex >= 0 && typeIndex < sTypes.length) {
404             return sTypes[typeIndex];
405         }
406         return null;
407     }
408 
409     // Index position matters: It is used to encode the specific type in parceling.
410     // Keep up-to-date with C++ side.
411     private static Class<?>[] sTypes = new Class[] {
412             IntentStarted.class,
413             IntentFailed.class,
414             ActivityLaunched.class,
415             ActivityLaunchFinished.class,
416             ActivityLaunchCancelled.class,
417             ReportFullyDrawn.class,
418     };
419 
420     public static class ActivityRecordProtoParcelable {
write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot, int flags)421         public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot,
422                 int flags) {
423             p.writeByteArray(activityRecordSnapshot);
424         }
425 
create(Parcel p)426         public static @ActivityRecordProto byte[] create(Parcel p) {
427             byte[] data = p.createByteArray();
428 
429             return data;
430         }
431     }
432 
433     public static class IntentProtoParcelable {
434         private static final int INTENT_PROTO_CHUNK_SIZE = 1024;
435 
write(Parcel p, @NonNull Intent intent, int flags)436         public static void write(Parcel p, @NonNull Intent intent, int flags) {
437             // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream,
438             // so create a new one every time.
439             final ProtoOutputStream protoOutputStream =
440                     new ProtoOutputStream(INTENT_PROTO_CHUNK_SIZE);
441             // Write this data out as the top-most IntentProto (i.e. it is not a sub-object).
442             intent.dumpDebug(protoOutputStream);
443             final byte[] bytes = protoOutputStream.getBytes();
444 
445             p.writeByteArray(bytes);
446         }
447 
448         // TODO: Should be mockable for testing?
449         // We cannot deserialize in the platform because we don't have a 'readFromProto'
450         // code.
create(Parcel p)451         public static @NonNull Intent create(Parcel p) {
452             // This will "read" the correct amount of data, but then we discard it.
453             byte[] data = p.createByteArray();
454 
455             // Never called by real code in a platform, this binder API is implemented only in C++.
456             return new Intent("<cannot deserialize IntentProto>");
457         }
458     }
459 }
460