/* * 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. */ #ifndef IORAP_BINDER_APP_LAUNCH_EVENT_H_ #define IORAP_BINDER_APP_LAUNCH_EVENT_H_ #include "binder/common.h" #include "common/introspection.h" #include "common/expected.h" #include #include #include // IntentProto #include // ActivityRecord namespace iorap { namespace binder { // These protos are part of the iorapd binder ABI, alias them for easier usage. using IntentProto = ::android::content::IntentProto; using ActivityRecordProto = ::com::android::server::wm::ActivityRecordProto; struct AppLaunchEvent : public ::android::Parcelable { // Index position matters: Keep up-to-date with AppLaunchEvent.java sTypes field. enum class Type : int32_t { kUninitialized = -1, kIntentStarted = 0, kIntentFailed = 1, kActivityLaunched = 2, kActivityLaunchFinished = 3, kActivityLaunchCancelled = 4, kReportFullyDrawn = 5, }; enum class Temperature : int32_t { kUninitialized = -1, kCold = 1, kWarm = 2, kHot = 3, }; Type type{Type::kUninitialized}; int64_t sequence_id{-1}; // kIntentStarted only. std::unique_ptr intent_proto; // kActivityLaunched only. Temperature temperature{Temperature::kUninitialized}; // kActivityLaunch*. Can be null in kActivityLaunchCancelled. std::unique_ptr activity_record_proto; // kIntentStarted, kActivityLaunchFinished and kReportFullyDrawn only. int64_t timestamp_nanos{-1}; AppLaunchEvent() = default; AppLaunchEvent(Type type, int64_t sequence_id, std::unique_ptr intent_proto = nullptr, Temperature temperature = Temperature::kUninitialized, std::unique_ptr activity_record_proto = nullptr, int64_t timestamp_nanos = -1) : type(type), sequence_id(sequence_id), intent_proto(std::move(intent_proto)), temperature(temperature), activity_record_proto(std::move(activity_record_proto)), timestamp_nanos(timestamp_nanos) { } AppLaunchEvent(const AppLaunchEvent& other) { *this = other; } AppLaunchEvent& operator=(const AppLaunchEvent& other) { if (&other == this) { return *this; } type = other.type; sequence_id = other.sequence_id; if (other.intent_proto != nullptr) { intent_proto.reset(new IntentProto(*other.intent_proto)); } temperature = other.temperature; if (other.activity_record_proto != nullptr) { activity_record_proto.reset(new ActivityRecordProto(*other.activity_record_proto)); } timestamp_nanos = other.timestamp_nanos; return *this; } ::android::status_t readFromParcel(const android::Parcel* parcel) override { # define PARCEL_READ_OR_RETURN(function, ...) \ if (::android::status_t res = function(__VA_ARGS__); res != ::android::NO_ERROR) { \ LOG(ERROR) << "AppLaunchEvent::readFromParcel failed"; \ return res; \ } int32_t type_int; PARCEL_READ_OR_RETURN(parcel->readInt32, &type_int); type = static_cast(type_int); LOG(VERBOSE) << "AppLaunchEvent::readFromParcel (type=" << type_int << ")"; PARCEL_READ_OR_RETURN(parcel->readInt64, &sequence_id); switch (type) { case Type::kIntentStarted: PARCEL_READ_OR_RETURN(readIntent, parcel); PARCEL_READ_OR_RETURN(parcel->readInt64, ×tamp_nanos); break; case Type::kIntentFailed: // No extra arguments. break; case Type::kActivityLaunched: { PARCEL_READ_OR_RETURN(readActivityRecordProto, parcel); int32_t temperature_int; PARCEL_READ_OR_RETURN(parcel->readInt32, &temperature_int); temperature = static_cast(temperature_int); break; } case Type::kActivityLaunchFinished: PARCEL_READ_OR_RETURN(readActivityRecordProto, parcel); PARCEL_READ_OR_RETURN(parcel->readInt64, ×tamp_nanos); break; case Type::kActivityLaunchCancelled: PARCEL_READ_OR_RETURN(readActivityRecordProtoNullable, parcel); break; case Type::kReportFullyDrawn: PARCEL_READ_OR_RETURN(readActivityRecordProto, parcel); PARCEL_READ_OR_RETURN(parcel->readInt64, ×tamp_nanos); break; default: return android::BAD_VALUE; } # undef PARCEL_READ_OR_RETURN return ::android::NO_ERROR; // TODO: std::variant + protobuf implementation in AutoParcelable. } #define PARCEL_WRITE_OR_RETURN(function, ...) \ if (::android::status_t res = function(__VA_ARGS__); res != ::android::NO_ERROR) { \ return res; \ } ::android::status_t writeToParcel(android::Parcel* parcel) const override { PARCEL_WRITE_OR_RETURN(parcel->writeInt32, static_cast(type)); PARCEL_WRITE_OR_RETURN(parcel->writeInt64, sequence_id); switch (type) { case Type::kIntentStarted: PARCEL_WRITE_OR_RETURN(writeIntent, parcel); PARCEL_WRITE_OR_RETURN(parcel->writeInt64, timestamp_nanos); break; case Type::kIntentFailed: // No extra arguments. break; case Type::kActivityLaunched: PARCEL_WRITE_OR_RETURN(writeActivityRecordProto, parcel); PARCEL_WRITE_OR_RETURN(parcel->writeInt32, static_cast(temperature)); break; case Type::kActivityLaunchFinished: PARCEL_WRITE_OR_RETURN(writeActivityRecordProto, parcel); PARCEL_WRITE_OR_RETURN(parcel->writeInt64, timestamp_nanos); break; case Type::kActivityLaunchCancelled: PARCEL_WRITE_OR_RETURN(writeActivityRecordProtoNullable, parcel); break; case Type::kReportFullyDrawn: PARCEL_WRITE_OR_RETURN(writeActivityRecordProtoNullable, parcel); PARCEL_WRITE_OR_RETURN(parcel->writeInt64, timestamp_nanos); break; default: DCHECK(false) << "attempted to write an uninitialized AppLaunchEvent to Parcel"; return android::BAD_VALUE; } #undef PARCEL_WRITE_OR_RETURN return android::NO_ERROR; } private: // Using 'unique_ptr' here because protobufs don't have a move constructor. Is there // a better way that is cheap to pass them around? template static expected, ::android::status_t> ReadProto(const android::Parcel* parcel) { DCHECK(parcel != nullptr); ::android::status_t res; std::vector byte_vector; if ((res = parcel->readByteVector(/*out*/&byte_vector)) != ::android::NO_ERROR) { return unexpected(res); } // TODO: we may want to do this without an extra copy, by parsing // the protobuf directly out of the parcel. const uint8_t* data = byte_vector.data(); const size_t size = byte_vector.size(); std::unique_ptr proto_ptr{new T{}}; if (!proto_ptr) { return unexpected(::android::NO_MEMORY); } if (!proto_ptr->ParseFromArray(data, size)) { return unexpected(::android::BAD_VALUE); } return proto_ptr; } template static expected, ::android::status_t> ReadNullableProto(const android::Parcel* parcel) { DCHECK(parcel != nullptr); bool value; ::android::status_t res; res = parcel->readBool(/*out*/&value); if (res != ::android::NO_ERROR) { return unexpected(res); } if (!value) { return std::unique_ptr{nullptr}; } return ReadProto(parcel); } template static ::android::status_t WriteProto(android::Parcel* parcel, const std::unique_ptr& proto) { DCHECK(parcel != nullptr); DCHECK(proto != nullptr); std::vector byte_vector; { const int serialized_size = proto->ByteSize(); byte_vector.resize(serialized_size); if (!proto->SerializeToArray(byte_vector.data(), serialized_size)) { return ::android::BAD_VALUE; } } ::android::status_t res; if ((res = parcel->writeByteVector(/*in*/byte_vector)) != ::android::NO_ERROR) { return res; } return ::android::NO_ERROR; } template static ::android::status_t WriteNullableProto(android::Parcel* parcel, const std::unique_ptr& maybe_proto) { bool value = (maybe_proto != nullptr); ::android::status_t res; res = parcel->writeBool(value); if (res != ::android::NO_ERROR) { return res; } if (!value) { return ::android::NO_ERROR; } return WriteProto(parcel, maybe_proto); } android::status_t readIntent(const android::Parcel* parcel) { expected, ::android::status_t> maybe_intent = ReadProto(parcel); if (maybe_intent) { intent_proto = std::move(maybe_intent.value()); return ::android::NO_ERROR; } else { return maybe_intent.error(); } } android::status_t readActivityRecordProto(const android::Parcel* parcel) { expected, ::android::status_t> maybe_record = ReadProto(parcel); if (maybe_record) { activity_record_proto = std::move(maybe_record.value()); return ::android::NO_ERROR; } else { return maybe_record.error(); } } android::status_t readActivityRecordProtoNullable(const android::Parcel* parcel) { expected, ::android::status_t> maybe_record = ReadNullableProto(parcel); if (maybe_record) { activity_record_proto = std::move(maybe_record.value()); return ::android::NO_ERROR; } else { return maybe_record.error(); } } android::status_t writeIntent(android::Parcel* parcel) const { return WriteProto(parcel, intent_proto); } android::status_t writeActivityRecordProto(android::Parcel* parcel) const { return WriteProto(parcel, activity_record_proto); } android::status_t writeActivityRecordProtoNullable(android::Parcel* parcel) const { return WriteNullableProto(parcel, activity_record_proto); } }; inline std::ostream& operator<<(std::ostream& os, const AppLaunchEvent::Type& type) { switch (type) { case AppLaunchEvent::Type::kUninitialized: os << "kUninitialized"; break; case AppLaunchEvent::Type::kIntentStarted: os << "kIntentStarted"; break; case AppLaunchEvent::Type::kIntentFailed: os << "kIntentFailed"; break; case AppLaunchEvent::Type::kActivityLaunched: os << "kActivityLaunched"; break; case AppLaunchEvent::Type::kActivityLaunchCancelled: os << "kActivityLaunchCancelled"; break; case AppLaunchEvent::Type::kActivityLaunchFinished: os << "kActivityLaunchFinished"; break; case AppLaunchEvent::Type::kReportFullyDrawn: os << "kReportFullyDrawn"; break; default: os << "(unknown)"; } return os; } inline std::ostream& operator<<(std::ostream& os, const AppLaunchEvent::Temperature& type) { switch (type) { case AppLaunchEvent::Temperature::kUninitialized: os << "kUninitialized"; break; case AppLaunchEvent::Temperature::kCold: os << "kCold"; break; case AppLaunchEvent::Temperature::kWarm: os << "kWarm"; break; case AppLaunchEvent::Temperature::kHot: os << "kHot"; break; default: os << "(unknown)"; } return os; } inline std::ostream& operator<<(std::ostream& os, const AppLaunchEvent& e) { os << "AppLaunchEvent{"; os << "type=" << e.type << ","; os << "sequence_id=" << e.sequence_id << ","; os << "intent_proto="; if (e.intent_proto == nullptr) { os << "(nullptr)"; } else { os << "(action=" << e.intent_proto->action() << ","; os << "component="; if (e.intent_proto->has_component()) { // $package/$class_name os << e.intent_proto->component().package_name() << "/" << e.intent_proto->component().class_name(); } else { os << "(no component)"; } os << ")"; } os << ","; os << "temperature=" << e.temperature << ","; os << ","; os << "activity_record_proto="; if (e.activity_record_proto == nullptr) { os << "(nullptr)"; } else { // title or component name. os << "'" << e.activity_record_proto->identifier().title() << "'"; } os << ","; os << "timestamp_nanos=" << e.timestamp_nanos << ","; os << ","; os << "}"; return os; } /* IORAP_INTROSPECT_ADAPT_STRUCT(AppLaunchEvent, type, sequence_id, intent_proto, temperature, activity_record_proto); */ } // namespace binder } // namespace iorap IORAP_JAVA_NAMESPACE_BINDER_TYPEDEF(AppLaunchEvent) #endif // IORAP_BINDER_APP_LAUNCH_EVENT_H_