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