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/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index f8fb4a6..4ba31b4 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -19,7 +19,7 @@
 #include "IncidentService.h"
 
 #include "FdBuffer.h"
-#include "PrivacyBuffer.h"
+#include "PrivacyFilter.h"
 #include "Reporter.h"
 #include "incidentd_util.h"
 #include "section_list.h"
@@ -35,9 +35,12 @@
 
 #include <unistd.h>
 
-enum { WHAT_RUN_REPORT = 1, WHAT_SEND_BACKLOG_TO_DROPBOX = 2 };
+enum {
+    WHAT_TAKE_REPORT = 1,
+    WHAT_SEND_BROADCASTS = 2
+};
 
-#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL)
+#define DEFAULT_DELAY_NS (1000000000LL)
 
 #define DEFAULT_BYTES_SIZE_LIMIT (20 * 1024 * 1024)        // 20MB
 #define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000)  // 1 Day
@@ -53,6 +56,7 @@
 namespace os {
 namespace incidentd {
 
+String16 const APPROVE_INCIDENT_REPORTS("android.permission.APPROVE_INCIDENT_REPORTS");
 String16 const DUMP_PERMISSION("android.permission.DUMP");
 String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS");
 
@@ -60,7 +64,14 @@
     uid_t callingUid = IPCThreadState::self()->getCallingUid();
     pid_t callingPid = IPCThreadState::self()->getCallingPid();
     if (callingUid == AID_ROOT || callingUid == AID_SHELL) {
-        // root doesn't have permission.DUMP if don't do this!
+        // Root and shell are ok.
+        return Status::ok();
+    }
+
+    if (checkCallingPermission(APPROVE_INCIDENT_REPORTS)) {
+        // Permission controller (this is a singleton permission that is always granted
+        // exactly for PermissionController) is allowed to access incident reports
+        // so it can show the user info about what they are approving.
         return Status::ok();
     }
 
@@ -81,8 +92,8 @@
     }
 
     // checking calling request uid permission.
-    switch (args.dest()) {
-        case DEST_LOCAL:
+    switch (args.getPrivacyPolicy()) {
+        case PRIVACY_POLICY_LOCAL:
             if (callingUid != AID_SHELL && callingUid != AID_ROOT) {
                 ALOGW("Calling pid %d and uid %d does not have permission to get local data.",
                       callingPid, callingUid);
@@ -91,7 +102,7 @@
                         "Calling process does not have permission to get local data.");
             }
             break;
-        case DEST_EXPLICIT:
+        case PRIVACY_POLICY_EXPLICIT:
             if (callingUid != AID_SHELL && callingUid != AID_ROOT && callingUid != AID_STATSD &&
                     callingUid != AID_SYSTEM) {
                 ALOGW("Calling pid %d and uid %d does not have permission to get explicit data.",
@@ -105,78 +116,79 @@
     return Status::ok();
 }
 
-// ================================================================================
-ReportRequestQueue::ReportRequestQueue() {}
-
-ReportRequestQueue::~ReportRequestQueue() {}
-
-void ReportRequestQueue::addRequest(const sp<ReportRequest>& request) {
-    unique_lock<mutex> lock(mLock);
-    mQueue.push_back(request);
-}
-
-sp<ReportRequest> ReportRequestQueue::getNextRequest() {
-    unique_lock<mutex> lock(mLock);
-    if (mQueue.empty()) {
-        return NULL;
-    } else {
-        sp<ReportRequest> front(mQueue.front());
-        mQueue.pop_front();
-        return front;
-    }
+static string build_uri(const string& pkg, const string& cls, const string& id) {
+    return "build_uri not implemented " + pkg + "/" + cls + "/" + id;
 }
 
 // ================================================================================
-ReportHandler::ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue,
-                             const sp<Throttler>& throttler)
-    : mBacklogDelay(DEFAULT_BACKLOG_DELAY_NS),
-      mHandlerLooper(handlerLooper),
-      mQueue(queue),
-      mThrottler(throttler) {}
+ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory,
+            const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
+            const sp<Throttler>& throttler)
+        :mLock(),
+         mWorkDirectory(workDirectory),
+         mBroadcaster(broadcaster),
+         mHandlerLooper(handlerLooper),
+         mBacklogDelay(DEFAULT_DELAY_NS),
+         mThrottler(throttler),
+         mBatch(new ReportBatch()) {
+}
 
-ReportHandler::~ReportHandler() {}
+ReportHandler::~ReportHandler() {
+}
 
 void ReportHandler::handleMessage(const Message& message) {
     switch (message.what) {
-        case WHAT_RUN_REPORT:
-            run_report();
+        case WHAT_TAKE_REPORT:
+            take_report();
             break;
-        case WHAT_SEND_BACKLOG_TO_DROPBOX:
-            send_backlog_to_dropbox();
+        case WHAT_SEND_BROADCASTS:
+            send_broadcasts();
             break;
     }
 }
 
-void ReportHandler::scheduleRunReport(const sp<ReportRequest>& request) {
-    mQueue->addRequest(request);
-    mHandlerLooper->removeMessages(this, WHAT_RUN_REPORT);
-    mHandlerLooper->sendMessage(this, Message(WHAT_RUN_REPORT));
+void ReportHandler::schedulePersistedReport(const IncidentReportArgs& args) {
+    mBatch->addPersistedReport(args);
+    mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
+    mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
 }
 
-void ReportHandler::scheduleSendBacklogToDropbox() {
+void ReportHandler::scheduleStreamingReport(const IncidentReportArgs& args,
+        const sp<IIncidentReportStatusListener>& listener, int streamFd) {
+    mBatch->addStreamingReport(args, listener, streamFd);
+    mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
+    mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
+}
+
+void ReportHandler::scheduleSendBacklog() {
     unique_lock<mutex> lock(mLock);
-    mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS;
-    schedule_send_backlog_to_dropbox_locked();
+    mBacklogDelay = DEFAULT_DELAY_NS;
+    schedule_send_broadcasts_locked();
 }
 
-void ReportHandler::schedule_send_backlog_to_dropbox_locked() {
-    mHandlerLooper->removeMessages(this, WHAT_SEND_BACKLOG_TO_DROPBOX);
-    mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, Message(WHAT_SEND_BACKLOG_TO_DROPBOX));
+void ReportHandler::schedule_send_broadcasts_locked() {
+    mHandlerLooper->removeMessages(this, WHAT_SEND_BROADCASTS);
+    mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, Message(WHAT_SEND_BROADCASTS));
 }
 
-void ReportHandler::run_report() {
-    sp<Reporter> reporter = new Reporter();
-
-    // Merge all of the requests into one that has all of the
-    // requested fields.
-    while (true) {
-        sp<ReportRequest> request = mQueue->getNextRequest();
-        if (request == NULL) {
-            break;
-        }
-        reporter->batch.add(request);
+void ReportHandler::take_report() {
+    // Cycle the batch
+    sp<ReportBatch> batch;
+    {
+        unique_lock<mutex> lock(mLock);
+        batch = mBatch;
+        mBatch = new ReportBatch();
     }
 
+    if (batch->empty()) {
+        // Nothing to do.
+        return;
+    }
+
+    sp<Reporter> reporter = new Reporter(mWorkDirectory, batch);
+
+    // TODO: Do we really want to clear the reports if we throttle?  Should we only throttle
+    // requests going to dropbox?  How do we reconcile throttling with testing?
     if (mThrottler->shouldThrottle()) {
         ALOGW("RunReport got throttled.");
         return;
@@ -185,46 +197,76 @@
     // Take the report, which might take a while. More requests might queue
     // up while we're doing this, and we'll handle them in their next batch.
     // TODO: We should further rate-limit the reports to no more than N per time-period.
+    // TODO: Move this inside reporter.
     size_t reportByteSize = 0;
-    Reporter::run_report_status_t reportStatus = reporter->runReport(&reportByteSize);
+    reporter->runReport(&reportByteSize);
+
     mThrottler->addReportSize(reportByteSize);
-    if (reportStatus == Reporter::REPORT_NEEDS_DROPBOX) {
+
+    // Kick off the next steps, one of which is to send any new or otherwise remaining
+    // approvals, and one of which is to send any new or remaining broadcasts.
+    {
         unique_lock<mutex> lock(mLock);
-        schedule_send_backlog_to_dropbox_locked();
+        schedule_send_broadcasts_locked();
     }
 }
 
-void ReportHandler::send_backlog_to_dropbox() {
-    if (Reporter::upload_backlog() == Reporter::REPORT_NEEDS_DROPBOX) {
+void ReportHandler::send_broadcasts() {
+    Broadcaster::broadcast_status_t result = mBroadcaster->sendBroadcasts();
+    if (result == Broadcaster::BROADCASTS_FINISHED) {
+        // We're done.
+        unique_lock<mutex> lock(mLock);
+        mBacklogDelay = DEFAULT_DELAY_NS;
+    } else if (result == Broadcaster::BROADCASTS_REPEAT) {
+        // It worked, but there are more.
+        unique_lock<mutex> lock(mLock);
+        mBacklogDelay = DEFAULT_DELAY_NS;
+        schedule_send_broadcasts_locked();
+    } else if (result == Broadcaster::BROADCASTS_BACKOFF) {
         // There was a failure. Exponential backoff.
         unique_lock<mutex> lock(mLock);
         mBacklogDelay *= 2;
         ALOGI("Error sending to dropbox. Trying again in %lld minutes",
               (mBacklogDelay / (1000000000LL * 60)));
-        schedule_send_backlog_to_dropbox_locked();
-    } else {
-        mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS;
+        schedule_send_broadcasts_locked();
     }
 }
 
 // ================================================================================
-IncidentService::IncidentService(const sp<Looper>& handlerLooper)
-    : mQueue(new ReportRequestQueue()),
-      mThrottler(new Throttler(DEFAULT_BYTES_SIZE_LIMIT, DEFAULT_REFACTORY_PERIOD_MS)) {
-    mHandler = new ReportHandler(handlerLooper, mQueue, mThrottler);
+IncidentService::IncidentService(const sp<Looper>& handlerLooper) {
+    mThrottler = new Throttler(DEFAULT_BYTES_SIZE_LIMIT, DEFAULT_REFACTORY_PERIOD_MS);
+    mWorkDirectory = new WorkDirectory();
+    mBroadcaster = new Broadcaster(mWorkDirectory);
+    mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper,
+            mThrottler);
+    mBroadcaster->setHandler(mHandler);
 }
 
 IncidentService::~IncidentService() {}
 
 Status IncidentService::reportIncident(const IncidentReportArgs& args) {
-    ALOGI("reportIncident");
+    // TODO: Validate that the privacy policy is one of the real ones.
+    // If it isn't, clamp it to the next more restrictive real one.
 
+    // TODO: This function should reject the LOCAL privacy policy.
+    // Those have to stream.
+
+    // TODO: Check that the broadcast recevier has the proper permissions
+    // TODO: Maybe we should consider relaxing the permissions if it's going to
+    // dropbox, but definitely not if it's going to the broadcaster.
     Status status = checkIncidentPermissions(args);
     if (!status.isOk()) {
         return status;
     }
 
-    mHandler->scheduleRunReport(new ReportRequest(args, NULL, -1));
+    // If they didn't specify a component, use dropbox.
+    IncidentReportArgs argsCopy(args);
+    if (argsCopy.receiverPkg().length() == 0 && argsCopy.receiverCls().length() == 0) {
+        argsCopy.setReceiverPkg(DROPBOX_SENTINEL.getPackageName());
+        argsCopy.setReceiverCls(DROPBOX_SENTINEL.getClassName());
+    }
+
+    mHandler->schedulePersistedReport(argsCopy);
 
     return Status::ok();
 }
@@ -232,19 +274,29 @@
 Status IncidentService::reportIncidentToStream(const IncidentReportArgs& args,
                                                const sp<IIncidentReportStatusListener>& listener,
                                                const unique_fd& stream) {
-    ALOGI("reportIncidentToStream");
+    // TODO: Validate that the privacy policy is one of the real ones.
+    // If it isn't, clamp it to the next more restrictive real one.
 
-    Status status = checkIncidentPermissions(args);
+    // TODO: Only shell should be able to do a LOCAL privacy policy report.
+
+    // Streaming reports can not also be broadcast.
+    IncidentReportArgs argsCopy(args);
+    argsCopy.setReceiverPkg("");
+    argsCopy.setReceiverCls("");
+
+    Status status = checkIncidentPermissions(argsCopy);
     if (!status.isOk()) {
         return status;
     }
 
+
+    // The ReportRequest takes ownership of the fd, so we need to dup it.
     int fd = dup(stream.get());
     if (fd < 0) {
         return Status::fromStatusT(-errno);
     }
 
-    mHandler->scheduleRunReport(new ReportRequest(args, listener, fd));
+    mHandler->scheduleStreamingReport(argsCopy, listener, fd);
 
     return Status::ok();
 }
@@ -256,7 +308,92 @@
     }
 
     // When system_server is up and running, schedule the dropbox task to run.
-    mHandler->scheduleSendBacklogToDropbox();
+    mBroadcaster->reset();
+    mHandler->scheduleSendBacklog();
+
+    return Status::ok();
+}
+
+Status IncidentService::getIncidentReportList(const String16& pkg16, const String16& cls16,
+            vector<String16>* result) {
+    status_t err;
+    const string pkg(String8(pkg16).string());
+    const string cls(String8(cls16).string());
+
+    // List the reports
+    vector<sp<ReportFile>> all;
+    err = mWorkDirectory->getReports(&all, 0);
+    if (err != NO_ERROR) {
+        return Status::fromStatusT(err);
+    }
+
+    // Find the ones that match pkg and cls.
+    for (sp<ReportFile>& file: all) {
+        err = file->loadEnvelope();
+        if (err != NO_ERROR) {
+            continue;
+        }
+        const ReportFileProto& envelope = file->getEnvelope();
+        size_t reportCount = envelope.report_size();
+        for (int reportIndex = 0; reportIndex < reportCount; reportIndex++) {
+            const ReportFileProto_Report& report = envelope.report(reportIndex);
+            if (pkg == report.pkg() && cls == report.cls()) {
+                result->push_back(String16(build_uri(pkg, cls, file->getId()).c_str()));
+                break;
+            }
+        }
+    }
+
+    return Status::ok();
+}
+
+Status IncidentService::getIncidentReport(const String16& pkg16, const String16& cls16,
+            const String16& id16, IncidentManager::IncidentReport* result) {
+    status_t err;
+
+    const string pkg(String8(pkg16).string());
+    const string cls(String8(cls16).string());
+    const string id(String8(id16).string());
+
+    IncidentReportArgs args;
+    sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, &args);
+    if (file != nullptr) {
+        int fd;
+        err = file->startFilteringData(&fd, args);
+        if (err != 0) {
+            ALOGW("Error reading data file that we think should exist: %s",
+                    file->getDataFileName().c_str());
+            return Status::ok();
+        }
+
+        result->setTimestampNs(file->getTimestampNs());
+        result->setPrivacyPolicy(file->getEnvelope().privacy_policy());
+        result->takeFileDescriptor(fd);
+    }
+
+    return Status::ok();
+}
+
+Status IncidentService::deleteIncidentReports(const String16& pkg16, const String16& cls16,
+            const String16& id16) {
+    const string pkg(String8(pkg16).string());
+    const string cls(String8(cls16).string());
+    const string id(String8(id16).string());
+
+    sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, nullptr);
+    if (file != nullptr) {
+        mWorkDirectory->commit(file, pkg, cls);
+    }
+    mBroadcaster->clearBroadcasts(pkg, cls, id);
+
+    return Status::ok();
+}
+
+Status IncidentService::deleteAllIncidentReports(const String16& pkg16) {
+    const string pkg(String8(pkg16).string());
+
+    mWorkDirectory->commitAll(pkg);
+    mBroadcaster->clearPackageBroadcasts(pkg);
 
     return Status::ok();
 }
@@ -354,7 +491,7 @@
 
 static void printPrivacy(const Privacy* p, FILE* out, String8 indent) {
     if (p == NULL) return;
-    fprintf(out, "%sid:%d, type:%d, dest:%d\n", indent.string(), p->field_id, p->type, p->dest);
+    fprintf(out, "%sid:%d, type:%d, dest:%d\n", indent.string(), p->field_id, p->type, p->policy);
     if (p->children == NULL) return;
     for (int i = 0; p->children[i] != NULL; i++) {  // NULL-terminated.
         printPrivacy(p->children[i], out, indent + "  ");
@@ -362,6 +499,8 @@
 }
 
 status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<String8>& args) {
+    (void)in;
+
     const int argCount = args.size();
     if (argCount >= 3) {
         String8 opt = args[1];
@@ -376,6 +515,7 @@
         if (opt == "print") {
             printPrivacy(p, out, String8(""));
         } else if (opt == "parse") {
+            /*
             FdBuffer buf;
             status_t error = buf.read(fileno(in), 60000);
             if (error != NO_ERROR) {
@@ -383,15 +523,17 @@
                 return error;
             }
             fprintf(err, "Read %zu bytes\n", buf.size());
-            PrivacyBuffer pBuf(p, buf.data());
+            PrivacyFilter pBuf(p, buf.data());
 
             PrivacySpec spec = PrivacySpec::new_spec(argCount > 3 ? atoi(args[3]) : -1);
             error = pBuf.strip(spec);
             if (error != NO_ERROR) {
-                fprintf(err, "Error strip pii fields with spec %d\n", spec.dest);
+                fprintf(err, "Error strip pii fields with spec %d\n", spec.policy);
                 return error;
             }
             return pBuf.flush(fileno(out));
+            */
+            return -1;
         }
     } else {
         return cmd_help(out);
@@ -408,7 +550,7 @@
 
     ALOGD("Dump incident proto");
     IncidentReportArgs incidentArgs;
-    incidentArgs.setDest(DEST_EXPLICIT);
+    incidentArgs.setPrivacyPolicy(PRIVACY_POLICY_EXPLICIT);
     int skipped[] = SKIPPED_SECTIONS;
     for (const Section** section = SECTION_LIST; *section; section++) {
         const int id = (*section)->id;
@@ -421,12 +563,16 @@
         return PERMISSION_DENIED;
     }
 
+    // The ReportRequest takes ownership of the fd, so we need to dup it.
     int fd1 = dup(fd);
     if (fd1 < 0) {
         return -errno;
     }
 
-    mHandler->scheduleRunReport(new ReportRequest(incidentArgs, NULL, fd1));
+    // TODO: Remove this.  Someone even dumpstate, wanting to get an incident report
+    // should use the API.  That will take making dumpstated call the API, which is a
+    // good thing.  It also means it won't be subject to the timeout.
+    mHandler->scheduleStreamingReport(incidentArgs, NULL, fd1);
 
     return NO_ERROR;
 }