blob: 8263e0af4422d9a983181ba82286fdd31a03712b [file] [log] [blame]
/*
* Copyright (C) 2018 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.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.proto.ProtoOutputStream;
import com.android.server.wm.ActivityMetricsLaunchObserver;
import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Objects;
/**
* Provide a hint to iorapd that an app launch sequence has transitioned state.<br /><br />
*
* Knowledge of when an activity starts/stops can be used by iorapd to increase system
* performance (e.g. by launching perfetto tracing to record an io profile, or by
* playing back an ioprofile via readahead) over the long run.<br /><br />
*
* /@see com.google.android.startop.iorap.IIorap#onAppLaunchEvent <br /><br />
* @see com.android.server.wm.ActivityMetricsLaunchObserver
* ActivityMetricsLaunchObserver for the possible event states.
* @hide
*/
public abstract class AppLaunchEvent implements Parcelable {
@LongDef
@Retention(RetentionPolicy.SOURCE)
public @interface SequenceId {}
public final @SequenceId
long sequenceId;
protected AppLaunchEvent(@SequenceId long sequenceId) {
this.sequenceId = sequenceId;
}
@Override
public boolean equals(Object other) {
if (other instanceof AppLaunchEvent) {
return equals((AppLaunchEvent) other);
}
return false;
}
protected boolean equals(AppLaunchEvent other) {
return sequenceId == other.sequenceId;
}
@Override
public String toString() {
return getClass().getSimpleName() +
"{" + "sequenceId=" + Long.toString(sequenceId) +
toStringBody() + "}";
}
protected String toStringBody() { return ""; };
// List of possible variants:
public static final class IntentStarted extends AppLaunchEvent {
@NonNull
public final Intent intent;
public final long timestampNs;
public IntentStarted(@SequenceId long sequenceId,
Intent intent,
long timestampNs) {
super(sequenceId);
this.intent = intent;
this.timestampNs = timestampNs;
Objects.requireNonNull(intent, "intent");
}
@Override
public boolean equals(Object other) {
if (other instanceof IntentStarted) {
return intent.equals(((IntentStarted)other).intent) &&
timestampNs == ((IntentStarted)other).timestampNs &&
super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
return ", intent=" + intent.toString() +
" , timestampNs=" + Long.toString(timestampNs);
}
@Override
protected void writeToParcelImpl(Parcel p, int flags) {
super.writeToParcelImpl(p, flags);
IntentProtoParcelable.write(p, intent, flags);
p.writeLong(timestampNs);
}
IntentStarted(Parcel p) {
super(p);
intent = IntentProtoParcelable.create(p);
timestampNs = p.readLong();
}
}
public static final class IntentFailed extends AppLaunchEvent {
public IntentFailed(@SequenceId long sequenceId) {
super(sequenceId);
}
@Override
public boolean equals(Object other) {
if (other instanceof IntentFailed) {
return super.equals(other);
}
return false;
}
IntentFailed(Parcel p) {
super(p);
}
}
public static abstract class BaseWithActivityRecordData extends AppLaunchEvent {
public final @NonNull
@ActivityRecordProto byte[] activityRecordSnapshot;
protected BaseWithActivityRecordData(@SequenceId long sequenceId,
@NonNull @ActivityRecordProto byte[] snapshot) {
super(sequenceId);
activityRecordSnapshot = snapshot;
Objects.requireNonNull(snapshot, "snapshot");
}
@Override
public boolean equals(Object other) {
if (other instanceof BaseWithActivityRecordData) {
return (Arrays.equals(activityRecordSnapshot,
((BaseWithActivityRecordData)other).activityRecordSnapshot)) &&
super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
return ", " + new String(activityRecordSnapshot);
}
@Override
protected void writeToParcelImpl(Parcel p, int flags) {
super.writeToParcelImpl(p, flags);
ActivityRecordProtoParcelable.write(p, activityRecordSnapshot, flags);
}
BaseWithActivityRecordData(Parcel p) {
super(p);
activityRecordSnapshot = ActivityRecordProtoParcelable.create(p);
}
}
public static final class ActivityLaunched extends BaseWithActivityRecordData {
public final @ActivityMetricsLaunchObserver.Temperature
int temperature;
public ActivityLaunched(@SequenceId long sequenceId,
@NonNull @ActivityRecordProto byte[] snapshot,
@ActivityMetricsLaunchObserver.Temperature int temperature) {
super(sequenceId, snapshot);
this.temperature = temperature;
}
@Override
public boolean equals(Object other) {
if (other instanceof ActivityLaunched) {
return temperature == ((ActivityLaunched)other).temperature &&
super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
return super.toStringBody() + ", temperature=" + Integer.toString(temperature);
}
@Override
protected void writeToParcelImpl(Parcel p, int flags) {
super.writeToParcelImpl(p, flags);
p.writeInt(temperature);
}
ActivityLaunched(Parcel p) {
super(p);
temperature = p.readInt();
}
}
public static final class ActivityLaunchFinished extends BaseWithActivityRecordData {
public final long timestampNs;
public ActivityLaunchFinished(@SequenceId long sequenceId,
@NonNull @ActivityRecordProto byte[] snapshot,
long timestampNs) {
super(sequenceId, snapshot);
this.timestampNs = timestampNs;
}
@Override
public boolean equals(Object other) {
if (other instanceof ActivityLaunchFinished) {
return timestampNs == ((ActivityLaunchFinished)other).timestampNs &&
super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
}
@Override
protected void writeToParcelImpl(Parcel p, int flags) {
super.writeToParcelImpl(p, flags);
p.writeLong(timestampNs);
}
ActivityLaunchFinished(Parcel p) {
super(p);
timestampNs = p.readLong();
}
}
public static class ActivityLaunchCancelled extends AppLaunchEvent {
public final @Nullable @ActivityRecordProto byte[] activityRecordSnapshot;
public ActivityLaunchCancelled(@SequenceId long sequenceId,
@Nullable @ActivityRecordProto byte[] snapshot) {
super(sequenceId);
activityRecordSnapshot = snapshot;
}
@Override
public boolean equals(Object other) {
if (other instanceof ActivityLaunchCancelled) {
return Arrays.equals(activityRecordSnapshot,
((ActivityLaunchCancelled)other).activityRecordSnapshot) &&
super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
return super.toStringBody() + ", " + new String(activityRecordSnapshot);
}
@Override
protected void writeToParcelImpl(Parcel p, int flags) {
super.writeToParcelImpl(p, flags);
if (activityRecordSnapshot != null) {
p.writeBoolean(true);
ActivityRecordProtoParcelable.write(p, activityRecordSnapshot, flags);
} else {
p.writeBoolean(false);
}
}
ActivityLaunchCancelled(Parcel p) {
super(p);
if (p.readBoolean()) {
activityRecordSnapshot = ActivityRecordProtoParcelable.create(p);
} else {
activityRecordSnapshot = null;
}
}
}
public static final class ReportFullyDrawn extends BaseWithActivityRecordData {
public final long timestampNs;
public ReportFullyDrawn(@SequenceId long sequenceId,
@NonNull @ActivityRecordProto byte[] snapshot,
long timestampNs) {
super(sequenceId, snapshot);
this.timestampNs = timestampNs;
}
@Override
public boolean equals(Object other) {
if (other instanceof ReportFullyDrawn) {
return timestampNs == ((ReportFullyDrawn)other).timestampNs &&
super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
}
@Override
protected void writeToParcelImpl(Parcel p, int flags) {
super.writeToParcelImpl(p, flags);
p.writeLong(timestampNs);
}
ReportFullyDrawn(Parcel p) {
super(p);
timestampNs = p.readLong();
}
}
@Override
public @ContentsFlags int describeContents() { return 0; }
@Override
public void writeToParcel(Parcel p, @WriteFlags int flags) {
p.writeInt(getTypeIndex());
writeToParcelImpl(p, flags);
}
public static Creator<AppLaunchEvent> CREATOR =
new Creator<AppLaunchEvent>() {
@Override
public AppLaunchEvent createFromParcel(Parcel source) {
int typeIndex = source.readInt();
Class<?> kls = getClassFromTypeIndex(typeIndex);
if (kls == null) {
throw new IllegalArgumentException("Invalid type index: " + typeIndex);
}
try {
return (AppLaunchEvent) kls.getConstructor(Parcel.class).newInstance(source);
} catch (InstantiationException e) {
throw new AssertionError(e);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
} catch (InvocationTargetException e) {
throw new AssertionError(e);
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
@Override
public AppLaunchEvent[] newArray(int size) {
return new AppLaunchEvent[0];
}
};
protected void writeToParcelImpl(Parcel p, int flags) {
p.writeLong(sequenceId);
}
protected AppLaunchEvent(Parcel p) {
sequenceId = p.readLong();
}
private int getTypeIndex() {
for (int i = 0; i < sTypes.length; ++i) {
if (sTypes[i].equals(this.getClass())) {
return i;
}
}
throw new AssertionError("sTypes did not include this type: " + this.getClass());
}
private static @Nullable Class<?> getClassFromTypeIndex(int typeIndex) {
if (typeIndex >= 0 && typeIndex < sTypes.length) {
return sTypes[typeIndex];
}
return null;
}
// Index position matters: It is used to encode the specific type in parceling.
// Keep up-to-date with C++ side.
private static Class<?>[] sTypes = new Class[] {
IntentStarted.class,
IntentFailed.class,
ActivityLaunched.class,
ActivityLaunchFinished.class,
ActivityLaunchCancelled.class,
ReportFullyDrawn.class,
};
public static class ActivityRecordProtoParcelable {
public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot,
int flags) {
p.writeByteArray(activityRecordSnapshot);
}
public static @ActivityRecordProto byte[] create(Parcel p) {
byte[] data = p.createByteArray();
return data;
}
}
public static class IntentProtoParcelable {
private static final int INTENT_PROTO_CHUNK_SIZE = 1024;
public static void write(Parcel p, @NonNull Intent intent, int flags) {
// There does not appear to be a way to 'reset' a ProtoOutputBuffer stream,
// so create a new one every time.
final ProtoOutputStream protoOutputStream =
new ProtoOutputStream(INTENT_PROTO_CHUNK_SIZE);
// Write this data out as the top-most IntentProto (i.e. it is not a sub-object).
intent.dumpDebug(protoOutputStream);
final byte[] bytes = protoOutputStream.getBytes();
p.writeByteArray(bytes);
}
// TODO: Should be mockable for testing?
// We cannot deserialize in the platform because we don't have a 'readFromProto'
// code.
public static @NonNull Intent create(Parcel p) {
// This will "read" the correct amount of data, but then we discard it.
byte[] data = p.createByteArray();
// Never called by real code in a platform, this binder API is implemented only in C++.
return new Intent("<cannot deserialize IntentProto>");
}
}
}