Snap for 12597713 from cc8ea7a889f4d9ab095a97f00782ee0c1d45dd35 to mainline-adservices-release

Change-Id: I5b0e6255c14dfe06793b7fadc9e11aebd84fc766
diff --git a/aidl/android/os/IProfilingService.aidl b/aidl/android/os/IProfilingService.aidl
index cebebae..c1b66c4 100644
--- a/aidl/android/os/IProfilingService.aidl
+++ b/aidl/android/os/IProfilingService.aidl
@@ -18,6 +18,7 @@
 
 import android.os.Bundle;
 import android.os.IProfilingResultCallback;
+import android.os.ProfilingTriggerValueParcel;
 
 /**
  * {@hide}
@@ -34,4 +35,12 @@
 
     oneway void receiveFileDescriptor(in ParcelFileDescriptor fileDescriptor, long keyMostSigBits, long keyLeastSigBits);
 
+    oneway void addProfilingTriggers(in List<ProfilingTriggerValueParcel> triggers, String packageName);
+
+    oneway void removeProfilingTriggers(in int[] triggers, String packageName);
+
+    oneway void clearProfilingTriggers(String packageName);
+
+    oneway void processTrigger(int uid, String packageName, int triggerType);
+
 }
diff --git a/aidl/android/os/ProfilingTriggerValueParcel.aidl b/aidl/android/os/ProfilingTriggerValueParcel.aidl
new file mode 100644
index 0000000..c9ebf2c
--- /dev/null
+++ b/aidl/android/os/ProfilingTriggerValueParcel.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+package android.os;
+
+/**
+ * {@hide}
+ */
+parcelable ProfilingTriggerValueParcel {
+    int triggerType;
+    int rateLimitingPeriodHours;
+}
\ No newline at end of file
diff --git a/framework/api/current.txt b/framework/api/current.txt
index fca446a..ae73bf5 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -2,7 +2,10 @@
 package android.os {
 
   @FlaggedApi("android.os.profiling.telemetry_apis") public final class ProfilingManager {
+    method @FlaggedApi("android.os.profiling.system_triggered_profiling_new") public void addProfilingTriggers(@NonNull java.util.List<android.os.ProfilingTrigger>);
+    method @FlaggedApi("android.os.profiling.system_triggered_profiling_new") public void clearProfilingTriggers();
     method public void registerForAllProfilingResults(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.ProfilingResult>);
+    method @FlaggedApi("android.os.profiling.system_triggered_profiling_new") public void removeProfilingTriggers(@NonNull java.util.List<java.lang.Integer>);
     method public void requestProfiling(int, @Nullable android.os.Bundle, @Nullable String, @Nullable android.os.CancellationSignal, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<android.os.ProfilingResult>);
     method public void unregisterForAllProfilingResults(@Nullable java.util.function.Consumer<android.os.ProfilingResult>);
     field public static final int PROFILING_TYPE_HEAP_PROFILE = 2; // 0x2
@@ -17,6 +20,7 @@
     method @Nullable public String getErrorMessage();
     method @Nullable public String getResultFilePath();
     method @Nullable public String getTag();
+    method @FlaggedApi("android.os.profiling.system_triggered_profiling_new") public int getTriggerType();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.ProfilingResult> CREATOR;
     field public static final int ERROR_FAILED_EXECUTING = 4; // 0x4
@@ -30,5 +34,19 @@
     field public static final int ERROR_UNKNOWN = 8; // 0x8
   }
 
+  @FlaggedApi("android.os.profiling.system_triggered_profiling_new") public final class ProfilingTrigger {
+    method public int getRateLimitingPeriodHours();
+    method public int getTriggerType();
+    field public static final int TRIGGER_TYPE_ANR = 2; // 0x2
+    field public static final int TRIGGER_TYPE_APP_COLD_START_ACTIVITY = 1; // 0x1
+    field public static final int TRIGGER_TYPE_NONE = 0; // 0x0
+  }
+
+  @FlaggedApi("android.os.profiling.system_triggered_profiling_new") public static final class ProfilingTrigger.Builder {
+    ctor public ProfilingTrigger.Builder(int);
+    method @NonNull public android.os.ProfilingTrigger build();
+    method @NonNull public android.os.ProfilingTrigger.Builder setRateLimitingPeriodHours(int);
+  }
+
 }
 
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 2f6a35a..2aff331 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -6,5 +6,10 @@
     method public static void setProfilingServiceManager(@NonNull android.os.ProfilingServiceManager);
   }
 
+  @FlaggedApi("android.os.profiling.system_triggered_profiling_new") public class ProfilingServiceHelper {
+    method @NonNull public static android.os.ProfilingServiceHelper getInstance();
+    method public void onProfilingTriggerOccurred(int, @NonNull String, int);
+  }
+
 }
 
diff --git a/framework/java/android/os/ProfilingManager.java b/framework/java/android/os/ProfilingManager.java
index 7a3f6e8..417c44e 100644
--- a/framework/java/android/os/ProfilingManager.java
+++ b/framework/java/android/os/ProfilingManager.java
@@ -33,14 +33,17 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
  * <p>
- * This class allows the caller to request profiling and listen for results. Profiling types
- * supported are: system traces, java heap dumps, heap profiles, and stack traces.
+ * This class allows the caller to:
+ * - Request profiling and listen for results. Profiling types supported are: system traces,
+ *      java heap dumps, heap profiles, and stack traces.
+ * - Register triggers for the system to capture profiling on the apps behalf.
  * </p>
  *
  * <p>
@@ -59,16 +62,27 @@
  * - A request-specific listener included with the request. This will trigger only with a result
  *     from the request it was provided with.
  * - A global listener provided by {@link #registerForAllProfilingResults}. This will be triggered
- *     for all results belonging to your app.
+ *     for all results belonging to your app. This listener is the only way to receive results from
+ *     system triggered profiling instances set up with {@link #addProfilingTriggers}.
  * </p>
  *
  * <p>
  * Requests are rate limited and not guaranteed to be filled. Rate limiting can be disabled for
- * local testing using the shell command
+ * local testing of {@link #requestProfiling} using the shell command
  * {@code device_config put profiling_testing rate_limiter.disabled true}
  * </p>
  *
  * <p>
+ * In order to test profiling triggers, enable testing mode for your app with the shell command
+ * {@code device_config put profiling_testing system_triggered_profiling.testing_package_name
+ * com.your.app} which will:
+ * - Ensure that a background trace is running.
+ * - Allow all triggers for the provided package name to pass the system level rate limiter.
+ * This mode will continue until manually stopped with the shell command
+ * {@code device_config delete profiling_testing system_triggered_profiling.testing_package_name}
+ * </p>
+ *
+ * <p>
  * Results are redacted and contain specific information about the requesting process only.
  * </p>
  */
@@ -256,7 +270,9 @@
                 if (service == null) {
                     executor.execute(() -> listener.accept(
                             new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag,
-                                "ProfilingService is not available")));
+                                "ProfilingService is not available",
+                                Flags.systemTriggeredProfilingNew()
+                                        ? ProfilingTrigger.TRIGGER_TYPE_NONE : 0)));
                     if (DEBUG) Log.d(TAG, "ProfilingService is not available");
                     return;
                 }
@@ -265,7 +281,9 @@
                 if (packageName == null) {
                     executor.execute(() -> listener.accept(
                             new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag,
-                                    "Failed to resolve package name")));
+                                    "Failed to resolve package name",
+                                    Flags.systemTriggeredProfilingNew()
+                                            ? ProfilingTrigger.TRIGGER_TYPE_NONE : 0)));
                     if (DEBUG) Log.d(TAG, "Failed to resolve package name.");
                     return;
                 }
@@ -293,7 +311,9 @@
                 if (DEBUG) Log.d(TAG, "Binder exception processing request", e);
                 executor.execute(() -> listener.accept(
                         new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag,
-                                "Binder exception processing request")));
+                                "Binder exception processing request",
+                                Flags.systemTriggeredProfilingNew()
+                                        ? ProfilingTrigger.TRIGGER_TYPE_NONE : 0)));
                 throw new RuntimeException("Unable to request profiling.");
             }
         }
@@ -323,7 +343,9 @@
                 // not ever be triggered.
                 executor.execute(() -> listener.accept(new ProfilingResult(
                         ProfilingResult.ERROR_UNKNOWN, null, null,
-                        "Binder exception processing request")));
+                        "Binder exception processing request",
+                        Flags.systemTriggeredProfilingNew()
+                                ? ProfilingTrigger.TRIGGER_TYPE_NONE : 0)));
                 return;
             }
             mCallbacks.add(new ProfilingRequestCallbackWrapper(executor, listener, null));
@@ -384,6 +406,146 @@
         }
     }
 
+    /**
+     * Register the provided list of triggers for this process.
+     *
+     * Profiling triggers are system triggered events that an app can register interest in receiving
+     * profiling of. There is no guarantee that these triggers will be filled. Results, if
+     * available, will be delivered only to a global listener added using
+     * {@link #registerForAllProfilingResults}.
+     *
+     * Only one of each trigger type can be added at a time.
+     * - If the provided list contains a trigger type that is already registered then the new one
+     *      will replace the existing one.
+     * - If the provided list contains more than one trigger object for a trigger type then only one
+     *      will be kept.
+     */
+    @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
+    public void addProfilingTriggers(@NonNull List<ProfilingTrigger> triggers) {
+        synchronized (mLock) {
+            if (triggers.isEmpty()) {
+                // No triggers are being added, nothing to do.
+                if (DEBUG) Log.d(TAG, "Trying to add an empty list of triggers.");
+                return;
+            }
+
+            final IProfilingService service = getOrCreateIProfilingServiceLocked(false);
+            if (service == null) {
+                // If we can't access service then we can't do anything. Return.
+                if (DEBUG) Log.d(TAG, "ProfilingService is not available, triggers will be lost.");
+                return;
+            }
+
+            String packageName = mContext.getPackageName();
+            if (packageName == null) {
+                if (DEBUG) Log.d(TAG, "Failed to resolve package name.");
+                return;
+            }
+
+            try {
+                service.addProfilingTriggers(toValueParcelList(triggers), packageName);
+            } catch (RemoteException e) {
+                if (DEBUG) Log.d(TAG, "Binder exception processing request", e);
+                throw new RuntimeException("Unable to add profiling triggers.");
+            }
+        }
+    }
+
+    @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
+    private List<ProfilingTriggerValueParcel> toValueParcelList(
+            List<ProfilingTrigger> triggerList) {
+        List<ProfilingTriggerValueParcel> triggerValueParcelList =
+                new ArrayList<ProfilingTriggerValueParcel>();
+
+        for (int i = 0; i < triggerList.size(); i++) {
+            triggerValueParcelList.add(triggerList.get(i).toValueParcel());
+        }
+
+        return triggerValueParcelList;
+    }
+
+    /** Remove the provided list of triggers for this process. */
+    @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
+    public void removeProfilingTriggers(@NonNull List<Integer> triggers) {
+        synchronized (mLock) {
+            if (triggers.isEmpty()) {
+                // No triggers are being removed, nothing to do.
+                if (DEBUG) Log.d(TAG, "Trying to remove an empty list of triggers.");
+                return;
+            }
+
+            final IProfilingService service = getOrCreateIProfilingServiceLocked(false);
+            if (service == null) {
+                // If we can't access service then we can't do anything. Return.
+                if (DEBUG) {
+                    Log.d(TAG, "ProfilingService is not available, triggers will not be removed.");
+                }
+                return;
+            }
+
+            String packageName = mContext.getPackageName();
+            if (packageName == null) {
+                if (DEBUG) Log.d(TAG, "Failed to resolve package name.");
+                return;
+            }
+
+            // First filter for valid triggers only.
+            ArrayList<Integer> validTriggers = new ArrayList<Integer>();
+            for (int i = 0; i < triggers.size(); i++) {
+                int trigger = triggers.get(i).intValue();
+                if (ProfilingTrigger.isValidRequestTriggerType(trigger)) {
+                    validTriggers.add(trigger);
+                }
+            }
+
+            if (validTriggers.isEmpty()) {
+                // No valid triggers are being removed, nothing to do.
+                if (DEBUG) Log.d(TAG, "Trying to remove a list of invalid triggers only.");
+                return;
+            }
+
+            // Move to array for binder.
+            int[] arr = new int[validTriggers.size()];
+            for (int i = 0; i < validTriggers.size(); i++) {
+                arr[i] = validTriggers.get(i).intValue();
+            }
+
+            try {
+                service.removeProfilingTriggers(arr, packageName);
+            } catch (RemoteException e) {
+                if (DEBUG) Log.d(TAG, "Binder exception processing request", e);
+                throw new RuntimeException("Unable to remove profiling triggers.");
+            }
+        }
+    }
+
+    /** Remove all triggers for this process. */
+    @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
+    public void clearProfilingTriggers() {
+        synchronized (mLock) {
+            final IProfilingService service = getOrCreateIProfilingServiceLocked(false);
+            if (service == null) {
+                // If we can't access service then we can't do anything. Return.
+                if (DEBUG) {
+                    Log.d(TAG, "ProfilingService is not available, triggers will not be removed.");
+                }
+                return;
+            }
+
+            String packageName = mContext.getPackageName();
+            if (packageName == null) {
+                if (DEBUG) Log.d(TAG, "Failed to resolve package name.");
+                return;
+            }
+
+            try {
+                service.clearProfilingTriggers(packageName);
+            } catch (RemoteException e) {
+                if (DEBUG) Log.d(TAG, "Binder exception processing request", e);
+                throw new RuntimeException("Unable to clear profiling triggers.");
+            }
+        }
+    }
 
     /** @hide */
     @VisibleForTesting
@@ -460,7 +622,10 @@
                                     wrapper.mExecutor.execute(() -> wrapper.mListener.accept(
                                             new ProfilingResult(overrideStatusToError
                                                     ? ProfilingResult.ERROR_UNKNOWN : status,
-                                                    getAppFileDir() + resultFile, tag, error)));
+                                                    getAppFileDir() + resultFile, tag, error,
+                                                    Flags.systemTriggeredProfilingNew()
+                                                            ? ProfilingTrigger.TRIGGER_TYPE_NONE
+                                                            : 0)));
                                 }
 
                                 // Remove the single listener that was tied to the request, if
diff --git a/framework/java/android/os/ProfilingResult.java b/framework/java/android/os/ProfilingResult.java
index 61357e1..0ed84ea 100644
--- a/framework/java/android/os/ProfilingResult.java
+++ b/framework/java/android/os/ProfilingResult.java
@@ -43,6 +43,9 @@
     /** @see #getErrorMessage */
     @Nullable final String mErrorMessage;
 
+    /** @see #getTriggerType */
+    final int mTriggerType;
+
     /** The request was executed and succeeded. */
     public static final int ERROR_NONE = 0;
 
@@ -85,11 +88,12 @@
     @interface ErrorCode {}
 
     ProfilingResult(@ErrorCode int errorCode, String resultFilePath, String tag,
-            String errorMessage) {
+            String errorMessage, int triggerType) {
         mErrorCode = errorCode;
         mResultFilePath = resultFilePath;
         mTag = tag;
         mErrorMessage = errorMessage;
+        mTriggerType = triggerType;
     }
 
     private ProfilingResult(@NonNull Parcel in) {
@@ -97,6 +101,7 @@
         mResultFilePath = in.readString();
         mTag = in.readString();
         mErrorMessage = in.readString();
+        mTriggerType = in.readInt();
     }
 
     @Override
@@ -105,6 +110,7 @@
         dest.writeString(mResultFilePath);
         dest.writeString(mTag);
         dest.writeString(mErrorMessage);
+        dest.writeInt(mTriggerType);
     }
 
     @Override
@@ -154,4 +160,13 @@
     public @Nullable String getErrorMessage() {
         return mErrorMessage;
     }
+
+    /**
+     * Trigger type that started this profiling, or {@link ProfilingTrigger#TRIGGER_TYPE_NONE} for
+     * profiling not started by a trigger.
+     */
+    @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
+    public int getTriggerType() {
+        return mTriggerType;
+    }
 }
diff --git a/framework/java/android/os/ProfilingServiceHelper.java b/framework/java/android/os/ProfilingServiceHelper.java
new file mode 100644
index 0000000..9c2c588
--- /dev/null
+++ b/framework/java/android/os/ProfilingServiceHelper.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+package android.os;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+import android.os.profiling.Flags;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Class for system to interact with {@link ProfilingService} to notify of trigger occurrences.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
+@SystemApi(client = Client.MODULE_LIBRARIES)
+public class ProfilingServiceHelper {
+    private static final String TAG = ProfilingServiceHelper.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final Object sLock = new Object();
+
+    @Nullable
+    @GuardedBy("sLock")
+    private static ProfilingServiceHelper sInstance;
+
+    private final Object mLock = new Object();
+
+    @NonNull
+    @GuardedBy("mLock")
+    private final IProfilingService mProfilingService;
+
+    private ProfilingServiceHelper(@NonNull IProfilingService service) {
+        mProfilingService = service;
+    }
+
+    /**
+     * Returns an instance of {@link ProfilingServiceHelper}.
+     *
+     * @throws IllegalStateException if called before ProfilingService is set up.
+     */
+    @NonNull
+    public static ProfilingServiceHelper getInstance() {
+        synchronized (sLock) {
+            if (sInstance != null) {
+                return sInstance;
+            }
+
+            IProfilingService service = Flags.telemetryApis() ? IProfilingService.Stub.asInterface(
+                    ProfilingFrameworkInitializer.getProfilingServiceManager()
+                            .getProfilingServiceRegisterer().get()) : null;
+
+            if (service == null) {
+                throw new IllegalStateException("ProfilingService not yet set up.");
+            }
+
+            sInstance = new ProfilingServiceHelper(service);
+
+            return sInstance;
+        }
+    }
+
+    /** Send a trigger to {@link ProfilingService}. */
+    public void onProfilingTriggerOccurred(int uid, @NonNull String packageName, int triggerType) {
+        synchronized (mLock) {
+            try {
+                mProfilingService.processTrigger(uid, packageName, triggerType);
+            } catch (RemoteException e) {
+                // Exception sending trigger to service. Nothing to do here, trigger will be lost.
+                if (DEBUG) Log.e(TAG, "Exception sending trigger", e);
+            }
+        }
+    }
+}
diff --git a/framework/java/android/os/ProfilingTrigger.java b/framework/java/android/os/ProfilingTrigger.java
new file mode 100644
index 0000000..7cfc113
--- /dev/null
+++ b/framework/java/android/os/ProfilingTrigger.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+package android.os;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.profiling.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Encapsulates a single profiling trigger.
+ */
+@FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
+public final class ProfilingTrigger {
+
+    /** No trigger. Used in {@link ProfilingResult} for non trigger caused results. */
+    public static final int TRIGGER_TYPE_NONE = 0;
+
+    /**
+     * Trigger occurs after a cold start has been completed for an activity component start. See
+     * {@link android.app.ApplicationStartInfo#getStartComponent} for more detail on start
+     * components.
+     */
+    public static final int TRIGGER_TYPE_APP_COLD_START_ACTIVITY = 1;
+
+    /** Trigger occurs after the app was killed due to an ANR */
+    public static final int TRIGGER_TYPE_ANR = 2;
+
+    /** @hide */
+    @IntDef(value = {
+        TRIGGER_TYPE_NONE,
+        TRIGGER_TYPE_APP_COLD_START_ACTIVITY,
+        TRIGGER_TYPE_ANR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface TriggerType {}
+
+    /** @see #getTriggerType */
+    private final @TriggerType int mTriggerType;
+
+    /** @see #getRateLimitingPeriodHours  */
+    private final int mRateLimitingPeriodHours;
+
+    private ProfilingTrigger(@TriggerType int triggerType, int rateLimitingPeriodHours) {
+        mTriggerType = triggerType;
+        mRateLimitingPeriodHours = rateLimitingPeriodHours;
+    }
+
+    /**
+     * Builder class to create a {@link ProfilingTrigger} object.
+     */
+    @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
+    public static final class Builder {
+        // Trigger type has to be set, so make it an object and set to null.
+        private int mBuilderTriggerType;
+
+        // Rate limiter period default is 0 which will make it do nothing.
+        private int mBuilderRateLimitingPeriodHours = 0;
+
+        /**
+         * Create a new builder instance to create a {@link ProfilingTrigger} object.
+         *
+         * Requires a trigger type. An app can only have one registered trigger per trigger type.
+         * Adding a new trigger with the same type will override the previously set one.
+         *
+         * @throws IllegalArgumentException if the trigger type is not valid.
+         */
+        public Builder(@TriggerType int triggerType) {
+            if (!isValidRequestTriggerType(triggerType)) {
+                throw new IllegalArgumentException("Invalid trigger type.");
+            }
+
+            mBuilderTriggerType = triggerType;
+        }
+
+        /** Build the {@link ProfilingTrigger} object. */
+        @NonNull
+        public ProfilingTrigger build() {
+            return new ProfilingTrigger(mBuilderTriggerType,
+                    mBuilderRateLimitingPeriodHours);
+        }
+
+        /**
+         * Set a rate limiting period in hours.
+         *
+         * The period is the minimum time the system should wait before providing another
+         * profiling result for the same trigger; actual time between events may be longer.
+         *
+         * If the rate limiting period is not provided or set to 0, no app-provided rate limiting
+         * will be used.
+         *
+         * This rate limiting is in addition to any system level rate limiting that may be applied.
+         *
+         * @throws IllegalArgumentException if the value is less than 0.
+         */
+        @NonNull
+        public Builder setRateLimitingPeriodHours(int rateLimitingPeriodHours) {
+            if (rateLimitingPeriodHours < 0) {
+                throw new IllegalArgumentException("Hours can't be negative. Try again.");
+            }
+
+            mBuilderRateLimitingPeriodHours = rateLimitingPeriodHours;
+            return this;
+        }
+    }
+
+    /** The trigger type indicates which event should trigger the requested profiling. */
+    public @TriggerType int getTriggerType() {
+        return mTriggerType;
+    }
+
+    /**
+     * The requester set rate limiting period in hours.
+     *
+     * The period is the minimum time the system should wait before providing another
+     * profiling result for the same trigger; actual time between events may be longer.
+     *
+     * If the rate limiting period is set to 0, no app-provided rate limiting will be used.
+     *
+     * This rate limiting is in addition to any system level rate limiting that may be applied.
+     */
+    public int getRateLimitingPeriodHours() {
+        return mRateLimitingPeriodHours;
+    }
+
+    /**
+     * Convert to value parcel. Used for binder.
+     *
+     * @hide
+     */
+    public ProfilingTriggerValueParcel toValueParcel() {
+        ProfilingTriggerValueParcel valueParcel = new ProfilingTriggerValueParcel();
+
+        valueParcel.triggerType = mTriggerType;
+        valueParcel.rateLimitingPeriodHours = mRateLimitingPeriodHours;
+
+        return valueParcel;
+    }
+
+    /**
+     * Check whether the trigger type is valid for request use. Note that this means that a value of
+     * {@link TRIGGER_TYPE_NONE} will return false.
+     *
+     * @hide
+     */
+    public static boolean isValidRequestTriggerType(int triggerType) {
+        return triggerType == TRIGGER_TYPE_APP_COLD_START_ACTIVITY
+                || triggerType == TRIGGER_TYPE_ANR;
+    }
+
+}
diff --git a/service/java/com/android/os/profiling/ProfilingService.java b/service/java/com/android/os/profiling/ProfilingService.java
index 33e602e..5b50ef8 100644
--- a/service/java/com/android/os/profiling/ProfilingService.java
+++ b/service/java/com/android/os/profiling/ProfilingService.java
@@ -34,6 +34,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.ProfilingManager;
 import android.os.ProfilingResult;
+import android.os.ProfilingTriggerValueParcel;
 import android.os.ProfilingTriggersWrapper;
 import android.os.QueuedResultsWrapper;
 import android.os.RemoteException;
@@ -1042,6 +1043,47 @@
     }
 
     /**
+     * Add the provided list of validated triggers with the provided package name and the callers
+     * uid being applied to all.
+     */
+    public void addProfilingTriggers(List<ProfilingTriggerValueParcel> triggers,
+            String packageName) {
+        int uid = Binder.getCallingUid();
+        for (int i = 0; i < triggers.size(); i++) {
+            ProfilingTriggerValueParcel trigger = triggers.get(i);
+            addTrigger(uid, packageName, trigger.triggerType, trigger.rateLimitingPeriodHours);
+        }
+    }
+
+    /**
+     * Remove the provided list of validated trigger codes from a process with the provided package
+     * name and the uid of the caller.
+     */
+    public void removeProfilingTriggers(int[] triggerTypesToRemove, String packageName) {
+        SparseArray<ProfilingTrigger> triggers =
+                mAppTriggers.get(packageName, Binder.getCallingUid());
+
+        for (int i = 0; i < triggerTypesToRemove.length; i++) {
+            int index = triggers.indexOfKey(triggerTypesToRemove[i]);
+            if (index >= 0) {
+                triggers.removeAt(index);
+            }
+        }
+
+        if (triggers.size() == 0) {
+            // Nothing left, remove.
+            mAppTriggers.remove(packageName, Binder.getCallingUid());
+        }
+    }
+
+    /**
+     * Remove all triggers from a process with the provided packagename and the uid of the caller.
+     */
+    public void clearProfilingTriggers(String packageName) {
+        mAppTriggers.remove(packageName, Binder.getCallingUid());
+    }
+
+    /**
      * Method called by manager, after creating a file from within application context, to send a
      * file descriptor for service to write the result of the profiling session to.
      *
diff --git a/tests/cts/src/android/profiling/cts/ProfilingFrameworkTests.java b/tests/cts/src/android/profiling/cts/ProfilingFrameworkTests.java
index acf60b4..21316d1 100644
--- a/tests/cts/src/android/profiling/cts/ProfilingFrameworkTests.java
+++ b/tests/cts/src/android/profiling/cts/ProfilingFrameworkTests.java
@@ -30,10 +30,13 @@
 
 import android.app.Instrumentation;
 import android.content.Context;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.ProfilingManager;
 import android.os.ProfilingResult;
+import android.os.ProfilingServiceHelper;
+import android.os.ProfilingTrigger;
 import android.os.profiling.DeviceConfigHelper;
 import android.os.profiling.Flags;
 import android.os.profiling.ProfilingService;
@@ -64,6 +67,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 
@@ -92,8 +96,13 @@
     private static final int RATE_LIMITER_WAIT_TIME_INCREMENTS_COUNT = 12;
 
     // Wait 2 seconds for profiling to get started before attempting to cancel it.
+    // TODO: b/376440094 - change to query perfetto and confirm profiling is running.
     private static final int WAIT_TIME_FOR_PROFILING_START_MS = 2 * 1000;
 
+    // Wait 10 seconds for profiling to potentially clone, process, and return result to confirm it
+    // did not occur.
+    private static final int WAIT_TIME_FOR_TRIGGERED_PROFILING_NO_RESULT = 10 * 1000;
+
     // Keep in sync with {@link ProfilingService} because we can't access it.
     private static final String OUTPUT_FILE_JAVA_HEAP_DUMP_SUFFIX = ".perfetto-java-heap-dump";
     private static final String OUTPUT_FILE_HEAP_PROFILE_SUFFIX = ".perfetto-heap-profile";
@@ -105,6 +114,11 @@
 
     private static final String COMMAND_OVERRIDE_DEVICE_CONFIG_INT = "device_config put %s %s %d";
     private static final String COMMAND_OVERRIDE_DEVICE_CONFIG_BOOL = "device_config put %s %s %b";
+    private static final String COMMAND_OVERRIDE_DEVICE_CONFIG_STRING =
+            "device_config put %s %s %s";
+    private static final String COMMAND_DELETE_DEVICE_CONFIG_STRING = "device_config delete %s %s";
+
+    private static final String REAL_PACKAGE_NAME = "com.android.profiling.tests";
 
     private static final int ONE_SECOND_MS = 1 * 1000;
     private static final int FIVE_SECONDS_MS = 5 * 1000;
@@ -143,8 +157,10 @@
 
     @SuppressWarnings("GuardedBy") // Suppress warning for mProfilingManager.mProfilingService lock.
     @After
-    public void cleanup() {
+    public void cleanup() throws Exception {
         mProfilingManager.mProfilingService = null;
+        executeShellCmd(COMMAND_DELETE_DEVICE_CONFIG_STRING, DeviceConfigHelper.NAMESPACE_TESTING,
+                DeviceConfigHelper.SYSTEM_TRIGGERED_TEST_PACKAGE_NAME);
     }
 
     /** Check and see if we can get a reference to the ProfilingManager service. */
@@ -862,6 +878,98 @@
         verify(mProfilingManager.mProfilingService, times(0)).generalListenerAdded();
     }
 
+    /**
+     * Test adding a profiling trigger and receiving a result works correctly.
+     *
+     * This is done by: adding the trigger through the public api, force starting a system triggered
+     * trace, sending a fake trigger as if from the system, and then confirming the result is
+     * received.
+     */
+    @SuppressWarnings("GuardedBy") // Suppress warning for mProfilingManager lock.
+    @Test
+    @RequiresFlagsEnabled(android.os.profiling.Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
+    public void testSystemTriggeredProfiling() throws Exception {
+        if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
+
+        // First add a trigger
+        ProfilingTrigger trigger = new ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_ANR)
+                .setRateLimitingPeriodHours(1)
+                .build();
+        mProfilingManager.addProfilingTriggers(List.of(trigger));
+
+        // And add a global listener
+        AppCallback callbackGeneral = new AppCallback();
+        mProfilingManager.registerForAllProfilingResults(
+                new ProfilingTestUtils.ImmediateExecutor(), callbackGeneral);
+
+        // Then start the system triggered trace for testing.
+        executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_STRING,
+                DeviceConfigHelper.NAMESPACE_TESTING,
+                DeviceConfigHelper.SYSTEM_TRIGGERED_TEST_PACKAGE_NAME,
+                REAL_PACKAGE_NAME);
+
+        // Wait a bit so the trace can get started and actually collect something.
+        sleep(WAIT_TIME_FOR_PROFILING_START_MS);
+
+        // Now fake a system trigger.
+        ProfilingServiceHelper.getInstance().onProfilingTriggerOccurred(Binder.getCallingUid(),
+                REAL_PACKAGE_NAME,
+                ProfilingTrigger.TRIGGER_TYPE_ANR);
+
+        // Wait for the trace to process.
+        waitForCallback(callbackGeneral);
+
+        // Finally, confirm that a result was received.
+        confirmCollectionSuccess(callbackGeneral.mResult, OUTPUT_FILE_TRACE_SUFFIX);
+    }
+
+    /**
+     * Test removing profiling trigger.
+     *
+     * There is no way to check the data structure from this context and that specifically is tested
+     * in {@link ProfilingServiceTests}, so this test just ensures that a result is not received.
+     */
+    @SuppressWarnings("GuardedBy") // Suppress warning for mProfilingManager lock.
+    @Test
+    @RequiresFlagsEnabled(android.os.profiling.Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
+    public void testSystemTriggeredProfilingRemove() throws Exception {
+        if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
+
+        // First add a trigger
+        ProfilingTrigger trigger = new ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_ANR)
+                .setRateLimitingPeriodHours(1)
+                .build();
+        mProfilingManager.addProfilingTriggers(List.of(trigger));
+
+        // And add a global listener
+        AppCallback callbackGeneral = new AppCallback();
+        mProfilingManager.registerForAllProfilingResults(
+                new ProfilingTestUtils.ImmediateExecutor(), callbackGeneral);
+
+        // Then start the system triggered trace for testing.
+        executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_STRING,
+                DeviceConfigHelper.NAMESPACE_TESTING,
+                DeviceConfigHelper.SYSTEM_TRIGGERED_TEST_PACKAGE_NAME,
+                REAL_PACKAGE_NAME);
+
+        // Wait a bit so the trace can get started and actually collect something.
+        sleep(WAIT_TIME_FOR_PROFILING_START_MS);
+
+        // Remove the trigger.
+        mProfilingManager.removeProfilingTriggers(List.of(ProfilingTrigger.TRIGGER_TYPE_ANR));
+
+        // Now fake a system trigger.
+        ProfilingServiceHelper.getInstance().onProfilingTriggerOccurred(Binder.getCallingUid(),
+                REAL_PACKAGE_NAME,
+                ProfilingTrigger.TRIGGER_TYPE_ANR);
+
+        // We can't wait for nothing to happen, so wait 10 seconds which should be long enough.
+        sleep(WAIT_TIME_FOR_TRIGGERED_PROFILING_NO_RESULT);
+
+        // Finally, confirm that no callback was received.
+        assertNull(callbackGeneral.mResult);
+    }
+
     /** Disable the rate limiter and wait long enough for the update to be picked up. */
     private void disableRateLimiter() {
         SystemUtil.runShellCommand(