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
+
+