incidentd can now handle multiple callers asking it for incident reports
Test: bit incident_test:* GtsIncidentManagerTestCases:*
Bug: 123543706
Change-Id: I9f671dd5d8b2ad139f952a23e575c2be16120459
diff --git a/cmds/incidentd/src/Broadcaster.cpp b/cmds/incidentd/src/Broadcaster.cpp
new file mode 100644
index 0000000..39e5393
--- /dev/null
+++ b/cmds/incidentd/src/Broadcaster.cpp
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "Log.h"
+
+#include "Broadcaster.h"
+
+#include "IncidentService.h"
+
+#include <android/os/DropBoxManager.h>
+#include <binder/IServiceManager.h>
+
+namespace android {
+namespace os {
+namespace incidentd {
+
+using android::os::IIncidentCompanion;
+using binder::Status;
+
+// ============================================================
+Broadcaster::ConsentListener::ConsentListener(const sp<Broadcaster>& broadcaster,
+ const ReportId& reportId)
+ :mBroadcaster(broadcaster),
+ mId(reportId) {
+}
+
+Broadcaster::ConsentListener::~ConsentListener() {
+}
+
+Status Broadcaster::ConsentListener::onReportApproved() {
+ mBroadcaster->report_approved(mId);
+ return Status::ok();
+}
+
+Status Broadcaster::ConsentListener::onReportDenied() {
+ mBroadcaster->report_denied(mId);
+ return Status::ok();
+}
+
+// ============================================================
+Broadcaster::ReportId::ReportId()
+ :id(),
+ pkg(),
+ cls() {
+}
+
+Broadcaster::ReportId::ReportId(const ReportId& that)
+ :id(that.id),
+ pkg(that.pkg),
+ cls(that.cls) {
+}
+
+Broadcaster::ReportId::ReportId(const string& i, const string& p, const string& c)
+ :id(i),
+ pkg(p),
+ cls(c) {
+}
+
+Broadcaster::ReportId::~ReportId() {
+}
+
+bool Broadcaster::ReportId::operator<(const ReportId& that) const {
+ if (id < that.id) {
+ return true;
+ }
+ if (id > that.id) {
+ return false;
+ }
+ if (pkg < that.pkg) {
+ return true;
+ }
+ if (pkg > that.pkg) {
+ return false;
+ }
+ if (cls < that.cls) {
+ return true;
+ }
+ return false;
+}
+
+// ============================================================
+Broadcaster::ReportStatus::ReportStatus()
+ :approval_sent(false),
+ ready_sent(false),
+ listener(nullptr) {
+}
+
+Broadcaster::ReportStatus::ReportStatus(const ReportStatus& that)
+ :approval_sent(that.approval_sent),
+ ready_sent(that.ready_sent),
+ listener(that.listener) {
+}
+
+Broadcaster::ReportStatus::~ReportStatus() {
+}
+
+// ============================================================
+Broadcaster::Broadcaster(const sp<WorkDirectory>& workDirectory)
+ :mReportHandler(),
+ mWorkDirectory(workDirectory) {
+}
+
+void Broadcaster::setHandler(const sp<ReportHandler>& handler) {
+ mReportHandler = handler;
+}
+
+void Broadcaster::reset() {
+ unique_lock<mutex> lock(mLock);
+ mLastSent = 0;
+ mHistory.clear();
+ // Could cancel the listeners, but this happens when
+ // the system process crashes, so don't bother.
+}
+
+void Broadcaster::clearBroadcasts(const string& pkg, const string& cls, const string& id) {
+ unique_lock<mutex> lock(mLock);
+
+ map<ReportId,ReportStatus>::const_iterator found = mHistory.find(ReportId(id, pkg, cls));
+ if (found != mHistory.end()) {
+ if (found->second.listener != nullptr) {
+ sp<IIncidentCompanion> ics = get_incident_companion();
+ if (ics != nullptr) {
+ ics->cancelAuthorization(found->second.listener);
+ }
+ }
+ mHistory.erase(found);
+ }
+}
+
+void Broadcaster::clearPackageBroadcasts(const string& pkg) {
+ unique_lock<mutex> lock(mLock);
+
+ map<ReportId,ReportStatus>::iterator it = mHistory.begin();
+ while (it != mHistory.end()) {
+ if (it->first.pkg == pkg) {
+ if (it->second.listener != nullptr) {
+ sp<IIncidentCompanion> ics = get_incident_companion();
+ if (ics != nullptr) {
+ ics->cancelAuthorization(it->second.listener);
+ }
+ }
+ it = mHistory.erase(it);
+ } else {
+ it++;
+ }
+ }
+}
+
+Broadcaster::broadcast_status_t Broadcaster::sendBroadcasts() {
+ int err;
+ int64_t lastSent = get_last_sent();
+
+ vector<sp<ReportFile>> files;
+ mWorkDirectory->getReports(&files, 0); //lastSent);
+
+ // Don't send multiple broadcasts to the same receiver.
+ set<ReportId> reportReadyBroadcasts;
+
+ for (const sp<ReportFile>& file: files) {
+ err = file->loadEnvelope();
+ if (err != NO_ERROR) {
+ ALOGW("Error (%s) loading envelope from %s", strerror(-err),
+ file->getEnvelopeFileName().c_str());
+ continue;
+ }
+
+ const ReportFileProto& envelope = file->getEnvelope();
+
+ if (!envelope.completed()) {
+ ALOGI("Incident report not completed skipping it: %s",
+ file->getEnvelopeFileName().c_str());
+ continue;
+ }
+
+ // When one of the broadcast functions in this loop fails, it's almost
+ // certainly because the system process is crashing or has crashed. Rather
+ // than continuing to pound on the system process and potentially make things
+ // worse, we bail right away, return BROADCASTS_BACKOFF, and we will try
+ // again later. In the meantime, if the system process did crash, it might
+ // clear out mHistory, which means we'll be back here again to send the
+ // backlog.
+ size_t reportCount = envelope.report_size();
+ bool hasApprovalPending = false;
+ for (int reportIndex = 0; reportIndex < reportCount; reportIndex++) {
+
+ const ReportFileProto_Report& report = envelope.report(reportIndex);
+ status_t err;
+ if (report.privacy_policy() == PRIVACY_POLICY_AUTOMATIC || report.share_approved()) {
+ // It's privacy policy is AUTO, or it's been approved,
+ // so send the actual broadcast.
+ if (!was_ready_sent(file->getId(), report.pkg(), report.cls())) {
+ if (report.pkg() == DROPBOX_SENTINEL.getPackageName()
+ && report.cls() == DROPBOX_SENTINEL.getClassName()) {
+ IncidentReportArgs args;
+ get_args_from_report(&args, report);
+ err = send_to_dropbox(file, args);
+ if (err != NO_ERROR) {
+ return BROADCASTS_BACKOFF;
+ }
+ } else {
+ reportReadyBroadcasts.insert(ReportId(file->getId(), report.pkg(),
+ report.cls()));
+ }
+ }
+ } else {
+ // It's not approved yet, so send the approval.
+ if (!was_approval_sent(file->getId(), report.pkg(), report.cls())) {
+ err = send_approval_broadcasts(file->getId(), report.pkg(), report.cls());
+ if (err != NO_ERROR) {
+ return BROADCASTS_BACKOFF;
+ }
+ hasApprovalPending = true;
+ }
+ }
+ }
+
+ lastSent = file->getTimestampNs();
+ if (!hasApprovalPending) {
+ set_last_sent(lastSent);
+ }
+ }
+
+ for (const ReportId& report: reportReadyBroadcasts) {
+ err = send_report_ready_broadcasts(report.id, report.pkg, report.cls);
+ if (err != NO_ERROR) {
+ return BROADCASTS_BACKOFF;
+ }
+ }
+
+ return mWorkDirectory->hasMore(lastSent) ? BROADCASTS_REPEAT : BROADCASTS_FINISHED;
+}
+
+void Broadcaster::set_last_sent(int64_t timestamp) {
+ unique_lock<mutex> lock(mLock);
+ mLastSent = timestamp;
+}
+
+int64_t Broadcaster::get_last_sent() {
+ unique_lock<mutex> lock(mLock);
+ return mLastSent;
+}
+
+/*
+void Broadcaster::printReportStatuses() const {
+ ALOGD("mHistory {");
+ for (map<ReportId,ReportStatus>::const_iterator it = mHistory.begin();
+ it != mHistory.end(); it++) {
+ ALOGD(" [%s %s] --> [%d %d]", it->first.id.c_str(), it->first.pkg.c_str(),
+ it->second.approval_sent, it->second.ready_sent);
+ }
+ ALOGD("}");
+}
+*/
+
+bool Broadcaster::was_approval_sent(const string& id, const string& pkg, const string& cls) {
+ unique_lock<mutex> lock(mLock);
+ map<ReportId,ReportStatus>::const_iterator found = mHistory.find(ReportId(id, pkg, cls));
+ if (found != mHistory.end()) {
+ return found->second.approval_sent;
+ }
+ return false;
+}
+
+void Broadcaster::set_approval_sent(const string& id, const string& pkg, const string& cls,
+ const sp<ConsentListener>& listener) {
+ unique_lock<mutex> lock(mLock);
+ ReportStatus& reportStatus = mHistory[ReportId(id, pkg, cls)];
+ reportStatus.approval_sent = true;
+ reportStatus.listener = listener;
+}
+
+bool Broadcaster::was_ready_sent(const string& id, const string& pkg, const string& cls) {
+ unique_lock<mutex> lock(mLock);
+ map<ReportId,ReportStatus>::const_iterator found = mHistory.find(ReportId(id, pkg, cls));
+ if (found != mHistory.end()) {
+ return found->second.ready_sent;
+ }
+ return false;
+}
+
+void Broadcaster::set_ready_sent(const string& id, const string& pkg, const string& cls) {
+ unique_lock<mutex> lock(mLock);
+ mHistory[ReportId(id, pkg, cls)].ready_sent = true;
+}
+
+status_t Broadcaster::send_approval_broadcasts(const string& id, const string& pkg,
+ const string& cls) {
+ sp<IIncidentCompanion> ics = get_incident_companion();
+ if (ics == nullptr) {
+ return NAME_NOT_FOUND;
+ }
+
+ sp<ConsentListener> listener = new ConsentListener(this, ReportId(id, pkg, cls));
+
+ ALOGI("send_approval_broadcasts for %s %s/%s", id.c_str(), pkg.c_str(), cls.c_str());
+
+ Status status = ics->authorizeReport(0, String16(pkg.c_str()),
+ String16(cls.c_str()), String16(id.c_str()), 0, listener);
+
+ if (!status.isOk()) {
+ // authorizeReport is oneway, so any error is a transaction error.
+ return status.transactionError();
+ }
+
+ set_approval_sent(id, pkg, cls, listener);
+
+ return NO_ERROR;
+}
+
+void Broadcaster::report_approved(const ReportId& reportId) {
+ status_t err;
+
+ // Kick off broadcaster to do send the ready broadcasts.
+ ALOGI("The user approved the report, so kicking off another broadcast pass. %s %s/%s",
+ reportId.id.c_str(), reportId.pkg.c_str(), reportId.cls.c_str());
+ sp<ReportFile> file = mWorkDirectory->getReport(reportId.pkg, reportId.cls, reportId.id,
+ nullptr);
+ if (file != nullptr) {
+ err = file->loadEnvelope();
+ if (err != NO_ERROR) {
+ return;
+ }
+
+ err = file->markApproved(reportId.pkg, reportId.cls);
+ if (err != NO_ERROR) {
+ ALOGI("Couldn't find report that was just approved: %s %s/%s",
+ reportId.id.c_str(), reportId.pkg.c_str(), reportId.cls.c_str());
+ return;
+ }
+
+ file->saveEnvelope();
+ if (err != NO_ERROR) {
+ return;
+ }
+ }
+ mReportHandler->scheduleSendBacklog();
+}
+
+void Broadcaster::report_denied(const ReportId& reportId) {
+ // The user didn't approve the report, so remove it from the WorkDirectory.
+ ALOGI("The user denied the report, so deleting it. %s %s/%s",
+ reportId.id.c_str(), reportId.pkg.c_str(), reportId.cls.c_str());
+ sp<ReportFile> file = mWorkDirectory->getReport(reportId.pkg, reportId.cls, reportId.id,
+ nullptr);
+ if (file != nullptr) {
+ mWorkDirectory->commit(file, reportId.pkg, reportId.cls);
+ }
+}
+
+status_t Broadcaster::send_report_ready_broadcasts(const string& id, const string& pkg,
+ const string& cls) {
+ sp<IIncidentCompanion> ics = get_incident_companion();
+ if (ics == nullptr) {
+ return NAME_NOT_FOUND;
+ }
+
+ ALOGI("send_report_ready_broadcasts for %s %s/%s", id.c_str(), pkg.c_str(), cls.c_str());
+
+ Status status = ics->sendReportReadyBroadcast(String16(pkg.c_str()), String16(cls.c_str()));
+
+ if (!status.isOk()) {
+ // sendReportReadyBroadcast is oneway, so any error is a transaction error.
+ return status.transactionError();
+ }
+
+ set_ready_sent(id, pkg, cls);
+
+ return NO_ERROR;
+}
+
+status_t Broadcaster::send_to_dropbox(const sp<ReportFile>& file,
+ const IncidentReportArgs& args) {
+ status_t err;
+
+ sp<DropBoxManager> dropbox = new DropBoxManager();
+ if (dropbox == nullptr) {
+ ALOGW("Can't reach dropbox now, so we won't be able to write the incident report to there");
+ return NO_ERROR;
+ }
+
+ // Start a thread to write the data to dropbox.
+ int readFd = -1;
+ err = file->startFilteringData(&readFd, args);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ // Takes ownership of readFd.
+ Status status = dropbox->addFile(String16("incident"), readFd, 0);
+ if (!status.isOk()) {
+ // TODO: This may or may not leak the readFd, depending on where it failed.
+ // Not sure how to fix this given the dropbox API.
+ ALOGW("Error sending incident report to dropbox.");
+ return -errno;
+ }
+
+ // On successful write, tell the working directory that this file is done.
+ mWorkDirectory->commit(file, DROPBOX_SENTINEL.getPackageName(),
+ DROPBOX_SENTINEL.getClassName());
+
+ // Don't need to call set_ready_sent, because we just removed it from the ReportFile,
+ // so we'll never hear about it again.
+
+ return NO_ERROR;
+}
+
+sp<IIncidentCompanion> Broadcaster::get_incident_companion() {
+ sp<IBinder> binder = defaultServiceManager()->getService(String16("incidentcompanion"));
+ if (binder == nullptr) {
+ ALOGI("Can not find IIncidentCompanion service to send broadcast. Will try again later.");
+ return nullptr;
+ }
+
+ sp<IIncidentCompanion> ics = interface_cast<IIncidentCompanion>(binder);
+ if (ics == nullptr) {
+ ALOGI("The incidentcompanion service is not an IIncidentCompanion. Will try again later.");
+ return nullptr;
+ }
+
+ return ics;
+}
+
+} // namespace incidentd
+} // namespace os
+} // namespace android
+
+