Public API: Incident report section registration

Mark incident report section registration and unregistration as public
APIs, and protect them with
android.permission.REGISTER_INCIDENT_REPORT_SECTION.

Bug: 144789854
Test: Build
Change-Id: I0d79ca12054292412e10a63ba2e6f45a149066a9
diff --git a/api/system-current.txt b/api/system-current.txt
index cb4c4b7..b3ba230 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -7326,8 +7326,11 @@
     method @Nullable @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.IncidentManager.IncidentReport getIncidentReport(android.net.Uri);
     method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public java.util.List<android.net.Uri> getIncidentReportList(String);
     method @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) public java.util.List<android.os.IncidentManager.PendingReport> getPendingReports();
+    method public void registerSection(int, @NonNull String, @NonNull android.os.IncidentManager.DumpCallback);
+    method public void registerSection(int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.IncidentManager.DumpCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void reportIncident(android.os.IncidentReportArgs);
     method @RequiresPermission("android.permission.REQUEST_INCIDENT_REPORT_APPROVAL") public void requestAuthorization(int, String, int, android.os.IncidentManager.AuthListener);
+    method public void unregisterSection(int);
     field public static final int FLAG_CONFIRMATION_DIALOG = 1; // 0x1
     field public static final int PRIVACY_POLICY_AUTO = 200; // 0xc8
     field public static final int PRIVACY_POLICY_EXPLICIT = 100; // 0x64
@@ -7340,6 +7343,11 @@
     method public void onReportDenied();
   }
 
+  public static class IncidentManager.DumpCallback {
+    ctor public IncidentManager.DumpCallback();
+    method public void onDumpSection(int, @NonNull java.io.OutputStream);
+  }
+
   public static class IncidentManager.IncidentReport implements java.io.Closeable android.os.Parcelable {
     ctor public IncidentManager.IncidentReport(android.os.Parcel);
     method public void close();
diff --git a/api/test-current.txt b/api/test-current.txt
index 190f9fe..4a86b8b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2033,8 +2033,11 @@
     method @Nullable @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.IncidentManager.IncidentReport getIncidentReport(android.net.Uri);
     method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public java.util.List<android.net.Uri> getIncidentReportList(String);
     method @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) public java.util.List<android.os.IncidentManager.PendingReport> getPendingReports();
+    method public void registerSection(int, @NonNull String, @NonNull android.os.IncidentManager.DumpCallback);
+    method public void registerSection(int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.IncidentManager.DumpCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void reportIncident(android.os.IncidentReportArgs);
     method @RequiresPermission("android.permission.REQUEST_INCIDENT_REPORT_APPROVAL") public void requestAuthorization(int, String, int, android.os.IncidentManager.AuthListener);
+    method public void unregisterSection(int);
     field public static final int FLAG_CONFIRMATION_DIALOG = 1; // 0x1
     field public static final int PRIVACY_POLICY_AUTO = 200; // 0xc8
     field public static final int PRIVACY_POLICY_EXPLICIT = 100; // 0x64
@@ -2047,6 +2050,11 @@
     method public void onReportDenied();
   }
 
+  public static class IncidentManager.DumpCallback {
+    ctor public IncidentManager.DumpCallback();
+    method public void onDumpSection(int, @NonNull java.io.OutputStream);
+  }
+
   public static class IncidentManager.IncidentReport implements java.io.Closeable android.os.Parcelable {
     ctor public IncidentManager.IncidentReport(android.os.Parcel);
     method public void close();
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 62312d1..6c2b855 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -350,11 +350,11 @@
 Status IncidentService::registerSection(const int id, const String16& name16,
         const sp<IIncidentDumpCallback>& callback) {
     const char* name = String8(name16).c_str();
-    ALOGI("Register section %d: %s", id, name);
+    const uid_t callingUid = IPCThreadState::self()->getCallingUid();
+    ALOGI("Uid %d registers section %d '%s'", callingUid, id, name);
     if (callback == nullptr) {
         return Status::fromExceptionCode(Status::EX_NULL_POINTER);
     }
-    const uid_t callingUid = IPCThreadState::self()->getCallingUid();
     for (int i = 0; i < mRegisteredSections.size(); i++) {
         if (mRegisteredSections.at(i)->id == id) {
             if (mRegisteredSections.at(i)->uid != callingUid) {
@@ -370,8 +370,9 @@
 }
 
 Status IncidentService::unregisterSection(const int id) {
-    ALOGI("Unregister section %d", id);
     uid_t callingUid = IPCThreadState::self()->getCallingUid();
+    ALOGI("Uid %d unregisters section %d", callingUid, id);
+
     for (auto it = mRegisteredSections.begin(); it != mRegisteredSections.end(); it++) {
         if ((*it)->id == id) {
             if ((*it)->uid != callingUid) {
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
index f6563eb..8dde117 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -36,6 +36,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -426,10 +427,9 @@
      *
      * @see #registerSection
      * @see #unregisterSection
-     *
-     * @hide
      */
     public static class DumpCallback {
+        private int mId;
         private Executor mExecutor;
 
         IIncidentDumpCallback.Stub mBinder = new IIncidentDumpCallback.Stub() {
@@ -437,20 +437,25 @@
             public void onDumpSection(ParcelFileDescriptor pfd) {
                 if (mExecutor != null) {
                     mExecutor.execute(() -> {
-                        DumpCallback.this.onDumpSection(
+                        DumpCallback.this.onDumpSection(mId,
                                 new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
                     });
                 } else {
-                    DumpCallback.this.onDumpSection(
+                    DumpCallback.this.onDumpSection(mId,
                             new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
                 }
             }
         };
 
         /**
-         * Called when incidentd requests to dump this section.
+         * Dump the registered section as a protobuf message to the given OutputStream. Called when
+         * incidentd requests to dump this section.
+         *
+         * @param id  the id of the registered section. The same id used in calling
+         *            {@link #registerSection(int, String, DumpCallback)} will be passed in here.
+         * @param out the OutputStream to write the protobuf message
          */
-        public void onDumpSection(OutputStream out) {
+        public void onDumpSection(int id, @NonNull OutputStream out) {
         }
     }
 
@@ -563,12 +568,20 @@
 
     /**
      * Register a callback to dump an extended incident report section with the given id and name.
-     * The callback function will be invoked when an incident report with all sections or sections
-     * matching the given id is being taken.
      *
-     * @hide
+     * Calling <code>registerSection</code> with a duplicate id will override previous registration.
+     * However, the request must come from the same calling uid.
+     *
+     * @param id       the ID of the extended section. It should be unique system-wide, and be
+     *                 different from IDs of all existing section in
+     *                 frameworks/base/core/proto/android/os/incident.proto.
+     *                 Also see incident.proto for other rules about the ID.
+     * @param name     the name to display in logs and/or stderr when taking an incident report
+     *                 containing this section, mainly for debugging purpose
+     * @param callback the callback function to be invoked when an incident report with all sections
+     *                 or sections matching the given id is being taken
      */
-    public void registerSection(int id, String name, @NonNull DumpCallback callback) {
+    public void registerSection(int id, @NonNull String name, @NonNull DumpCallback callback) {
         registerSection(id, name, mContext.getMainExecutor(), callback);
     }
 
@@ -576,16 +589,27 @@
      * Register a callback to dump an extended incident report section with the given id and name,
      * running on the supplied executor.
      *
-     * @hide
+     * @param id       the ID of the extended section. It should be unique system-wide, and be
+     *                 different from IDs of all existing section in
+     *                 frameworks/base/core/proto/android/os/incident.proto.
+     *                 Also see incident.proto for other rules about the ID.
+     * @param name     the name to display in logs and/or stderr when taking an incident report
+     *                 containing this section, mainly for debugging purpose
+     * @param executor the executor used to run the callback
+     * @param callback the callback function to be invoked when an incident report with all sections
+     *                 or sections matching the given id is being taken
      */
-    public void registerSection(int id, String name, @NonNull @CallbackExecutor Executor executor,
-            @NonNull DumpCallback callback) {
+    public void registerSection(int id, @NonNull String name,
+                @NonNull @CallbackExecutor Executor executor, @NonNull DumpCallback callback) {
+        Objects.requireNonNull(executor, "executor cannot be null");
+        Objects.requireNonNull(callback, "callback cannot be null");
         try {
             if (callback.mExecutor != null) {
                 throw new RuntimeException("Do not reuse DumpCallback objects when calling"
                         + " registerSection");
             }
             callback.mExecutor = executor;
+            callback.mId = id;
             final IIncidentManager service = getIIncidentManagerLocked();
             if (service == null) {
                 Slog.e(TAG, "registerSection can't find incident binder service");
@@ -599,9 +623,7 @@
 
     /**
      * Unregister an extended section dump function. The section must be previously registered with
-     * {@link #registerSection(int, String, DumpCallback)}
-     *
-     * @hide
+     * {@link #registerSection(int, String, DumpCallback)} by the same calling uid.
      */
     public void unregisterSection(int id) {
         try {
@@ -719,7 +741,7 @@
                 throw new RuntimeException("Invalid URI: No "
                         + URI_PARAM_REPORT_ID + " parameter. " + uri);
             }
-        
+
             try {
                 getCompanionServiceLocked().deleteIncidentReports(pkg, cls, id);
             } catch (RemoteException ex) {