Snap for 11520864 from ba25268dce0bf716ec24242f6ddf9000f5dbd5b3 to 24Q2-release

Change-Id: I53782cb958552150084cbe56b97f089a33dddfaf
diff --git a/framework/java/android/os/ProfilingManager.java b/framework/java/android/os/ProfilingManager.java
index 29b448d..c8c89b4 100644
--- a/framework/java/android/os/ProfilingManager.java
+++ b/framework/java/android/os/ProfilingManager.java
@@ -52,13 +52,14 @@
     private static final String TAG = ProfilingManager.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    private static final Object sLock = new Object();
+    private final Object mLock = new Object();
     private final Context mContext;
 
-    @GuardedBy("sLock")
-    private final ArrayList<ProfilingRequestCallbackWrapper> mCallbacks = new ArrayList<>();
+    /** @hide */
+    @GuardedBy("mLock")
+    public final ArrayList<ProfilingRequestCallbackWrapper> mCallbacks = new ArrayList<>();
 
-    @GuardedBy("sLock")
+    @GuardedBy("mLock")
     private IProfilingService mProfilingService;
 
     /**
@@ -108,7 +109,7 @@
             @Nullable CancellationSignal cancellationSignal,
             @Nullable Executor executor,
             @Nullable Consumer<ProfilingResult> listener) {
-        synchronized (sLock) {
+        synchronized (mLock) {
             try {
                 final UUID key = UUID.randomUUID();
 
@@ -130,6 +131,7 @@
                     if (DEBUG) Log.d(TAG, "ProfilingService is not available");
                     return;
                 }
+
                 // For key, use most and least signifcant bits so we can create an identical UUID
                 // after passing over binder.
                 service.requestProfiling(profilingRequest, mContext.getFilesDir().getPath(), tag,
@@ -137,7 +139,7 @@
                 if (cancellationSignal != null) {
                     cancellationSignal.setOnCancelListener(
                         () -> {
-                            synchronized (sLock) {
+                            synchronized (mLock) {
                                 try {
                                     service.requestCancel(key.getMostSignificantBits(),
                                             key.getLeastSignificantBits());
@@ -168,7 +170,7 @@
     public void registerForAllProfilingResults(
             @NonNull Executor executor,
             @NonNull Consumer<ProfilingResult> listener) {
-        synchronized (sLock) {
+        synchronized (mLock) {
             mCallbacks.add(new ProfilingRequestCallbackWrapper(executor, listener, null));
         }
     }
@@ -183,7 +185,7 @@
      */
     public void unregisterForAllProfilingResults(
             @Nullable Consumer<ProfilingResult> listener) {
-        synchronized (sLock) {
+        synchronized (mLock) {
             if (mCallbacks.isEmpty()) {
                 // No callbacks, nothing to remove.
                 return;
@@ -215,7 +217,7 @@
     }
 
     @TargetApi(35)
-    @GuardedBy("sLock")
+    @GuardedBy("mLock")
     private @Nullable IProfilingService getIProfilingServiceLocked() {
         if (mProfilingService != null) {
             return mProfilingService;
@@ -238,7 +240,7 @@
                 public void sendResult(@Nullable String resultFile, long keyMostSigBits,
                         long keyLeastSigBits, int status, @Nullable String tag,
                         @Nullable String error) {
-                    synchronized (sLock) {
+                    synchronized (mLock) {
                         if (mCallbacks.isEmpty()) {
                             // This shouldn't happen - no callbacks, nowhere to report this result.
                             if (DEBUG) Log.d(TAG, "No callbacks");
diff --git a/framework/proto/android/os/profiling_request.proto b/framework/proto/android/os/profiling_request.proto
index fcf21ec..f8cb046 100644
--- a/framework/proto/android/os/profiling_request.proto
+++ b/framework/proto/android/os/profiling_request.proto
@@ -14,7 +14,9 @@
   message StackSampling {
     optional uint32 frequency = 1;
   }
-  message SystemTrace {}
+  message SystemTrace {
+    optional uint32 duration_ms = 1;
+  }
   message Config {
     oneof cfg_type {
       JavaHeapDump java_heap_dump = 1;
diff --git a/service/Android.bp b/service/Android.bp
index bd875c5..dc5cc61 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -31,6 +31,7 @@
     libs: [
         "framework-annotations-lib",
         "framework-profiling.impl",
+        "framework-configinfrastructure",
     ],
     static_libs: [
         "modules-utils-build",
diff --git a/service/java/com/android/os/profiling/Configs.java b/service/java/com/android/os/profiling/Configs.java
index 454928a..5eb2757 100644
--- a/service/java/com/android/os/profiling/Configs.java
+++ b/service/java/com/android/os/profiling/Configs.java
@@ -21,6 +21,9 @@
 import java.lang.IllegalArgumentException;
 
 public final class Configs {
+
+    static final String HEAP_PROFILE_ART = "heaps: \"com.android.art\"";
+
     static final String CONFIG_HEAP_PROFILE = "buffers {\n"
         + "  size_kb: 65536\n"
         + "}\n"
@@ -33,14 +36,13 @@
         + "      shmem_size_bytes: 8388608\n"
         + "      sampling_interval_bytes: {{sampling_interval}}\n"
         + "      process_cmdline: \"{{package_name}}\"\n"
-        + "      {% if {{art}} %}\n"
-        + "      heaps: \"com.android.art\"\n"
-        + "      {% endif %}\n"
+        + "      {{art}}\n"
         + "    }\n"
         + "  }\n"
         + "}\n"
         + "\n"
-        + "flush_timeout_ms: 30000";
+        + "flush_timeout_ms: 30000\n"
+        + "duration_ms: {{duration}}";
     static final String CONFIG_JAVA_HEAP_DUMP = "buffers {\n"
         + "  # This is the maximum size of the trace. The buffer will be mmap'd but, only\n"
         + "  # the non empty pages contribute to RSS.\n"
@@ -186,8 +188,12 @@
         + "duration_ms: {{duration}}";
 
     // Time to wait beyond trace timeout to ensure perfetto has time to finish writing output.
-    private static final int FILE_PROCESSING_DELAY_MS = 1000;
+    private static final int FILE_PROCESSING_DELAY_MS = 5000;
 
+    private static final int DEFAULT_HEAP_PROFILE_DURATION_MS = 2 * 60 * 1000;
+    // 1 second duration + 100 seconds max wait for dump to finish.
+    private static final int DEFAULT_JAVA_HEAP_DUMP_DURATION_MS = (1 + 100) * 1000;
+    private static final int DEFAULT_STACK_SAMPLING_DURATION_MS = 60 * 1000;
     private static final int DEFAULT_TRACE_DURATION_MS = 5 * 60 * 1000;
     private static final long DEFAULT_HEAP_PROFILE_SAMPLING_INTERVAL = 4096;
     private static final int DEFAULT_STACK_SAMPLING_FREQUENCY = 100;
@@ -226,15 +232,19 @@
                     ? heapProfile.getSamplingIntervalBytes() : sDefaultHeapProfileSamplingInterval;
             result = CONFIG_HEAP_PROFILE
                     .replace("{{sampling_interval}}", String.valueOf(samplingIntervalBytes))
-                    .replace("{{art}}", String.valueOf(art));
+                    .replace("{{art}}", art ? HEAP_PROFILE_ART : "")
+                    .replace("{{duration}}", String.valueOf(DEFAULT_HEAP_PROFILE_DURATION_MS));
         } else if (config.hasStackSampling()) {
             ProfilingRequest.StackSampling stackSampling = config.getStackSampling();
             int frequency = stackSampling.hasFrequency()
                     ? stackSampling.getFrequency() : sDefaultStackSamplingFrequency;
             result = CONFIG_STACK_SAMPLING.replace("{{frequency}}", String.valueOf(frequency));
         } else if (config.hasSystemTrace()) {
+            ProfilingRequest.SystemTrace systemTrace = config.getSystemTrace();
+            int durationMs = systemTrace.hasDurationMs() ? systemTrace.getDurationMs()
+                    : sDefaultTraceDurationMs;
             result = CONFIG_SYSTEM_TRACE.replace("{{duration}}",
-                    String.valueOf(sDefaultTraceDurationMs));
+                    String.valueOf(durationMs));
             // TODO: remove when redaction is hooked up b/327423523
             throw new IllegalArgumentException("Trace is not supported until redaction lands");
         }
@@ -253,8 +263,28 @@
      * in the event that it's not stopped manually.
      */
     public static int getPostProcessingScheduleDelayMs(ProfilingRequest request) {
-        // TODO select timeout based on type
-        // TODO adjust timeout/logic to ensure perfetto is finished
-        return sDefaultTraceDurationMs + FILE_PROCESSING_DELAY_MS;
+        // TODO: b/327660454 adjust timeout/logic to ensure perfetto is finished
+        if (!request.hasConfig()) {
+            // Proto has no config, not requesting anything.
+            throw new IllegalArgumentException("Proto config is missing");
+        }
+
+        ProfilingRequest.Config config = request.getConfig();
+
+        // Config can have at most one collection type, find out which and then determine time.
+        if (config.hasJavaHeapDump()) {
+            return DEFAULT_JAVA_HEAP_DUMP_DURATION_MS + FILE_PROCESSING_DELAY_MS;
+        } else if (config.hasHeapProfile()) {
+            return DEFAULT_HEAP_PROFILE_DURATION_MS + FILE_PROCESSING_DELAY_MS;
+        } else if (config.hasStackSampling()) {
+            return DEFAULT_STACK_SAMPLING_DURATION_MS + FILE_PROCESSING_DELAY_MS;
+        } else if (config.hasSystemTrace()) {
+            ProfilingRequest.SystemTrace systemTrace = config.getSystemTrace();
+            return (systemTrace.hasDurationMs() ? systemTrace.getDurationMs()
+                    : sDefaultTraceDurationMs) + FILE_PROCESSING_DELAY_MS;
+        }
+
+        // Proto config has no type, we don't know what the app wants.
+        throw new IllegalArgumentException("Proto config type is missing");
     }
-}
\ No newline at end of file
+}
diff --git a/service/java/com/android/os/profiling/ProfilingService.java b/service/java/com/android/os/profiling/ProfilingService.java
index ef6c51b..1e4ab37 100644
--- a/service/java/com/android/os/profiling/ProfilingService.java
+++ b/service/java/com/android/os/profiling/ProfilingService.java
@@ -71,21 +71,24 @@
     private final int PERFETTO_DESTROY_TIMEOUT_MS;
 
     private final Context mContext;
+    @VisibleForTesting public RateLimiter mRateLimiter = null;
 
     private final HandlerThread mHandlerThread = new HandlerThread("ProfilingService");
     private Handler mHandler;
 
+
     // uid indexed collecion of JNI callbacks for results.
-    private @Nullable SparseArray<IProfilingResultCallback> mResultCallbacks = new SparseArray<>();
+    @VisibleForTesting
+    public SparseArray<IProfilingResultCallback> mResultCallbacks = new SparseArray<>();
 
     // Request UUID key indexed storage of active tracing sessions. Currently only 1 active session
     // is supported at a time, but this will be used in future to support multiple.
-    private ArrayMap<String, TracingSession> mTracingSessions = new ArrayMap<>();
+    @VisibleForTesting
+    public ArrayMap<String, TracingSession> mTracingSessions = new ArrayMap<>();
 
     @VisibleForTesting
     public ProfilingService(Context context) {
         mContext = context;
-        RateLimiter.loadFromDisk();
         PERFETTO_DESTROY_TIMEOUT_MS = PERFETTO_DESTROY_DEFAULT_TIMEOUT_MS;
         mHandlerThread.start();
     }
@@ -134,7 +137,9 @@
         }
 
         // Check with rate limiter if this request is allowed.
-        final int status = RateLimiter.isProfilingRequestAllowed(Binder.getCallingUid(), request);
+        final int status = getRateLimiter().isProfilingRequestAllowed(Binder.getCallingUid(),
+                request);
+        if (DEBUG) Log.d(TAG, "Rate limiter status: " + status);
         if (status == RateLimiter.RATE_LIMIT_RESULT_ALLOWED) {
             // Rate limiter approved, try to start the request.
             try {
@@ -248,7 +253,7 @@
         getHandler().postDelayed(session.getProcessResultRunnable(), postProcessingDelayMs);
     }
 
-    public void stopProfiling(String key) throws RuntimeException {
+    private void stopProfiling(String key) throws RuntimeException {
         TracingSession session = mTracingSessions.get(key);
         if (session == null || session.getActiveTrace() == null) {
             if (DEBUG) Log.d(TAG, "No active trace, nothing to stop.");
@@ -424,6 +429,13 @@
         return mHandler;
     }
 
+    private RateLimiter getRateLimiter() {
+        if (mRateLimiter == null) {
+            mRateLimiter = new RateLimiter(mContext);
+        }
+        return mRateLimiter;
+    }
+
     public static final class Lifecycle extends SystemService {
         final ProfilingService mService;
 
diff --git a/service/java/com/android/os/profiling/RateLimiter.java b/service/java/com/android/os/profiling/RateLimiter.java
index 2fdcf42..456560d 100644
--- a/service/java/com/android/os/profiling/RateLimiter.java
+++ b/service/java/com/android/os/profiling/RateLimiter.java
@@ -17,20 +17,26 @@
 package android.os.profiling;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.Context;
 import android.os.ProfilingRequest;
 import android.os.ProfilingResult;
+import android.provider.DeviceConfig;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayDeque;
 import java.util.Queue;
+import java.util.concurrent.Executors;
 
-public final class RateLimiter {
+public class RateLimiter {
 
     private static final String DEVICE_CONFIG_NAMESPACE = "profiling";
-
-    private static final long PERSIST_TO_DISK_FREQUENCY_MS;
+    private static final String DEVICE_CONFIG_RATE_LIMITER_DISABLE_PROPERTY
+            = "rate_limiter.disabled";
 
     private static final long TIME_1_HOUR_MS = 60 * 60 * 1000;
     private static final long TIME_24_HOUR_MS = 24 * 60 * 60 * 1000;
@@ -40,17 +46,23 @@
     public static final int RATE_LIMIT_RESULT_BLOCKED_PROCESS = 1;
     public static final int RATE_LIMIT_RESULT_BLOCKED_SYSTEM = 2;
 
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+    private final long mPersistToDiskFrequency;
+
     /** To be disabled for testing only. */
-    private static boolean sRateLimiterEnabled = true;
+    @GuardedBy("mLock")
+    private boolean mRateLimiterEnabled = true;
 
     /** Collection of run costs and entries from the last hour. */
-    private static final EntryGroupWrapper sPastRuns1Hour;
+    private final EntryGroupWrapper mPastRuns1Hour;
     /** Collection of run costs and entries from the 24 hours. */
-    private static final EntryGroupWrapper sPastRuns24Hour;
+    private final EntryGroupWrapper mPastRuns24Hour;
     /** Collection of run costs and entries from the 7 days. */
-    private static final EntryGroupWrapper sPastRuns7Day;
+    private final EntryGroupWrapper mPastRuns7Day;
 
-    private static long sLastPersistedTimestampMs;
+    private long mLastPersistedTimestampMs;
 
     @IntDef(value={
         RATE_LIMIT_RESULT_ALLOWED,
@@ -60,55 +72,77 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface RateLimitResult {}
 
-    static {
+    public RateLimiter(Context context) {
         // TODO: b/324885858 use DeviceConfig for adjustable values.
-        sPastRuns1Hour = new EntryGroupWrapper(10, 10, TIME_1_HOUR_MS);
-        sPastRuns24Hour = new EntryGroupWrapper(100, 100, TIME_24_HOUR_MS);
-        sPastRuns7Day = new EntryGroupWrapper(1000, 1000, TIME_7_DAY_MS);
-        PERSIST_TO_DISK_FREQUENCY_MS = 0;
-        sLastPersistedTimestampMs = System.currentTimeMillis();
+        mContext = context;
+        mPastRuns1Hour = new EntryGroupWrapper(10, 10, TIME_1_HOUR_MS);
+        mPastRuns24Hour = new EntryGroupWrapper(100, 100, TIME_24_HOUR_MS);
+        mPastRuns7Day = new EntryGroupWrapper(1000, 1000, TIME_7_DAY_MS);
+        mPersistToDiskFrequency = 0;
+        mLastPersistedTimestampMs = System.currentTimeMillis();
+        loadFromDisk();
+
+        // Get initial value for whether rate limiter should be enforcing or if it should always
+        // allow profiling requests. This is used for (automated and manual) testing only.
+        synchronized (mLock) {
+            mRateLimiterEnabled = !DeviceConfig.getBoolean(DEVICE_CONFIG_NAMESPACE,
+                DEVICE_CONFIG_RATE_LIMITER_DISABLE_PROPERTY, false);
+        }
+        // Now subscribe to updates on rate limiter enforcing config.
+        DeviceConfig.addOnPropertiesChangedListener(DEVICE_CONFIG_NAMESPACE,
+                mContext.getMainExecutor(), new DeviceConfig.OnPropertiesChangedListener() {
+                    @Override
+                    public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+                        synchronized (mLock) {
+                            mRateLimiterEnabled = properties.getBoolean(
+                                    DEVICE_CONFIG_RATE_LIMITER_DISABLE_PROPERTY, false);
+                        }
+                    }
+                });
     }
 
-    public static @RateLimitResult int isProfilingRequestAllowed(int uid,
+    public @RateLimitResult int isProfilingRequestAllowed(int uid,
             ProfilingRequest request) {
-        if (!sRateLimiterEnabled) {
-            // Rate limiter is disabled for testing, approve request and don't store cost.
-            return RATE_LIMIT_RESULT_ALLOWED;
+        synchronized (mLock) {
+            if (!mRateLimiterEnabled) {
+                // Rate limiter is disabled for testing, approve request and don't store cost.
+                return RATE_LIMIT_RESULT_ALLOWED;
+            }
+            final int cost = 1; // TODO: compute cost b/293957254
+            final long currentTimeMillis = System.currentTimeMillis();
+            int status = mPastRuns1Hour.isProfilingAllowed(uid, cost, currentTimeMillis);
+            if (status == RATE_LIMIT_RESULT_ALLOWED) {
+                status = mPastRuns24Hour.isProfilingAllowed(uid, cost, currentTimeMillis);
+            }
+            if (status == RATE_LIMIT_RESULT_ALLOWED) {
+                status = mPastRuns7Day.isProfilingAllowed(uid, cost, currentTimeMillis);
+            }
+            if (status == RATE_LIMIT_RESULT_ALLOWED) {
+                mPastRuns1Hour.add(uid, cost, currentTimeMillis);
+                mPastRuns24Hour.add(uid, cost, currentTimeMillis);
+                mPastRuns7Day.add(uid, cost, currentTimeMillis);
+                maybePersistToDisk();
+                return RATE_LIMIT_RESULT_ALLOWED;
+            }
+            return status;
         }
-        final int cost = 1; // TODO: compute cost b/293957254
-        final long currentTimeMillis = System.currentTimeMillis();
-        int status = sPastRuns1Hour.isProfilingAllowed(uid, cost, currentTimeMillis);
-        if (status == RATE_LIMIT_RESULT_ALLOWED) {
-            status = sPastRuns24Hour.isProfilingAllowed(uid, cost, currentTimeMillis);
-        }
-        if (status == RATE_LIMIT_RESULT_ALLOWED) {
-            status = sPastRuns7Day.isProfilingAllowed(uid, cost, currentTimeMillis);
-        }
-        if (status == RATE_LIMIT_RESULT_ALLOWED) {
-            sPastRuns1Hour.add(uid, cost, currentTimeMillis);
-            sPastRuns24Hour.add(uid, cost, currentTimeMillis);
-            sPastRuns7Day.add(uid, cost, currentTimeMillis);
-            maybePersistToDisk();
-            return RATE_LIMIT_RESULT_ALLOWED;
-        }
-        return status;
     }
 
-    static void maybePersistToDisk() {
-        if (PERSIST_TO_DISK_FREQUENCY_MS == 0
-                || System.currentTimeMillis() - sLastPersistedTimestampMs
-                >= PERSIST_TO_DISK_FREQUENCY_MS) {
+    void maybePersistToDisk() {
+        if (mPersistToDiskFrequency == 0
+                || System.currentTimeMillis() - mLastPersistedTimestampMs
+                >= mPersistToDiskFrequency) {
             persistToDisk();
         } else {
             // TODO: queue persist job b/293957254
         }
     }
 
-    static void persistToDisk() {
+    void persistToDisk() {
         // TODO: b/293957254
     }
 
-    static void loadFromDisk() {
+    void loadFromDisk() {
         // TODO: b/293957254
     }
 
diff --git a/tests/cts/src/android/profiling/cts/ProfilingServiceTests.java b/tests/cts/src/android/profiling/cts/ProfilingServiceTests.java
index 75aa014..b19233d 100644
--- a/tests/cts/src/android/profiling/cts/ProfilingServiceTests.java
+++ b/tests/cts/src/android/profiling/cts/ProfilingServiceTests.java
@@ -18,17 +18,48 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.runner.AndroidJUnit4;
+import static org.mockito.Mockito.reset;
 
+import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.os.IProfilingResultCallback;
+import android.os.ParcelFileDescriptor;
+import android.os.Binder;
+import android.content.pm.PackageManager;
+import android.app.UiAutomation;
+import android.os.profiling.RateLimiter;
+import android.os.profiling.TracingSession;
+import android.os.ProfilingRequest;
+import android.os.ProfilingResult;
 import android.os.profiling.ProfilingService;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
+import java.lang.Process;
+import java.util.UUID;
+
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
 import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 /**
  * Tests in this class are for testing the ProfilingService directly without the need to get a
@@ -37,24 +68,253 @@
 
 @RunWith(AndroidJUnit4.class)
 public final class ProfilingServiceTests {
+
+    private static final String APP_FILE_PATH = "/data/user/0/com.profiling.test/files";
+    private static final String APP_PACKAGE_NAME = "com.profiling.test";
+    private static final String REQUEST_TAG = "some unique string";
+
+    // Key most and least significant bits are used to generate a unique key specific to each
+    // request. Key is used to pair request back to caller and callbacks so test to keep consistent.
+    private static final long KEY_MOST_SIG_BITS = 456l;
+    private static final long KEY_LEAST_SIG_BITS = 123l;
+
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-    private ProfilingService mProfilingService =
-            new ProfilingService(ApplicationProvider.getApplicationContext());
 
-    @Test
-    public void createProfilingServiceTest() {
-    assertThat(mProfilingService).isNotNull();
-  }
+    @Mock private PackageManager mPackageManager;
+    @Mock private Process mActiveTrace;
 
+    private Context mContext = ApplicationProvider.getApplicationContext();
+    private ProfilingService mProfilingService;
+    private RateLimiter mRateLimiter;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mProfilingService = spy(new ProfilingService(mContext));
+        mRateLimiter = spy(new RateLimiter(mContext));
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+        mProfilingService.mRateLimiter = mRateLimiter;
+        doReturn(APP_PACKAGE_NAME).when(mPackageManager).getNameForUid(anyInt());
+    }
+
+    /** Test that registering binder callbacks works as expected. */
     @Test
-    public void profilingNotRunningTests() {
-      try {
-        boolean isRunning = mProfilingService.areAnyTracesRunning();
-        assertThat(isRunning).isFalse();
-      } catch (Exception exception) {
-        assertThat(exception).isInstanceOf(RuntimeException.class);
-      }
+    public void testRegisterResultCallback() {
+        ProfilingResultCallback callback = new ProfilingResultCallback();
+
+        // Register callback.
+        mProfilingService.registerResultsCallback(callback);
+
+        // Confirm callback is registered.
+        assertEquals(callback, mProfilingService.mResultCallbacks.get(Binder.getCallingUid()));
+    }
+
+    /**
+     * Test that requesting profiling while another profiling is in progress fails with correct
+     * error codes.
+     */
+    @Test
+    public void testRequestProfiling_ProfilingRunning_Fails() {
+        // Mock traces running check to simulate collection running.
+        doReturn(true).when(mProfilingService).areAnyTracesRunning();
+
+        // Register callback.
+        ProfilingResultCallback callback = new ProfilingResultCallback();
+        mProfilingService.registerResultsCallback(callback);
+
+        // Kick off request.
+        mProfilingService.requestProfiling(ProfilingTestUtils.getJavaHeapDumpProfilingRequest(),
+                APP_FILE_PATH, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+        // Confirm result matches failure expectation.
+        confirmResultCallback(callback, null, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS,
+                ProfilingResult.ERROR_FAILED_PROFILING_IN_PROGRESS, REQUEST_TAG, false);
+    }
+
+    /**
+     * Test that requesting profiling with an invalid request byte array fails with correct error
+     * codes.
+     */
+    @Test
+    public void testRequestProfiling_InvalidRequest_Fails() {
+        // Bypass traces running check, we're not testing that here.
+        doReturn(false).when(mProfilingService).areAnyTracesRunning();
+
+        // Register callback.
+        ProfilingResultCallback callback = new ProfilingResultCallback();
+        mProfilingService.registerResultsCallback(callback);
+
+        // Kick off request.
+        mProfilingService.requestProfiling(new byte[4], APP_FILE_PATH, REQUEST_TAG,
+                KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+        // Confirm result matches failure expectation.
+        confirmResultCallback(callback, null, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS,
+                ProfilingResult.ERROR_FAILED_INVALID_REQUEST, REQUEST_TAG, true);
+    }
+
+    /** Test that requesting where we cannot access the package name fails. */
+    @Test
+    public void testRequestProfiling_PackageNameNotFound_Fails() {
+        // Mock getNameForUid to simulate failure case.
+        doReturn(null).when(mPackageManager).getNameForUid(anyInt());
+
+        // Register callback.
+        ProfilingResultCallback callback = new ProfilingResultCallback();
+        mProfilingService.registerResultsCallback(callback);
+
+        // Kick off request.
+        mProfilingService.requestProfiling(ProfilingTestUtils.getJavaHeapDumpProfilingRequest(),
+                APP_FILE_PATH, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+        // Confirm result matches failure expectation.
+        confirmResultCallback(callback, null, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS,
+                ProfilingResult.ERROR_UNKNOWN, REQUEST_TAG, true);
+    }
+
+    /** Test that failing rate limiting blocks trace from running. */
+    @Test
+    public void testRequestProfiling_RateLimitBlocked_Fails() {
+        // Bypass traces running check, we're not testing that here.
+        doReturn(false).when(mProfilingService).areAnyTracesRunning();
+
+        // Mock rate limiter result to simulate failure case.
+        doReturn(RateLimiter.RATE_LIMIT_RESULT_BLOCKED_PROCESS).when(mRateLimiter)
+              .isProfilingRequestAllowed(anyInt(), anyObject());
+
+        // Register callback.
+        ProfilingResultCallback callback = new ProfilingResultCallback();
+        mProfilingService.registerResultsCallback(callback);
+
+        // Kick off request.
+        mProfilingService.requestProfiling(ProfilingTestUtils.getJavaHeapDumpProfilingRequest(),
+                APP_FILE_PATH, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+        // Confirm result matches failure expectation.
+        confirmResultCallback(callback, null, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS,
+                ProfilingResult.ERROR_FAILED_RATE_LIMIT_PROCESS, REQUEST_TAG, false);
+    }
+
+    /**
+     * Test profiling request with no issues makes it to perfetto kick off and fails because we're
+     * using the wrong context in these tests.
+     */
+    @Test
+    public void testRequestProfiling_Allowed_PerfettoPermissions_Fails() {
+        // Bypass traces running check, we're not testing that here.
+        doReturn(false).when(mProfilingService).areAnyTracesRunning();
+
+        // Register callback.
+        ProfilingResultCallback callback = new ProfilingResultCallback();
+        mProfilingService.registerResultsCallback(callback);
+
+        // Kick off request.
+        mProfilingService.requestProfiling(ProfilingTestUtils.getJavaHeapDumpProfilingRequest(),
+                APP_FILE_PATH, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+        // Perfetto cannot be run from this context, ensure it was attempted and failed permissions.
+        confirmResultCallback(callback, null, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS,
+                ProfilingResult.ERROR_UNKNOWN, REQUEST_TAG, true);
+        assertEquals("Perfetto error", callback.mError);
+    }
+
+    /** Test that checking if any traces are running works when trace is running. */
+    @Test
+    public void testAreAnyTracesRunning_True() {
+        // Ensure no active tracing sessions tracked.
+        mProfilingService.mTracingSessions.clear();
+        assertFalse(mProfilingService.areAnyTracesRunning());
+
+        // Create a tracing session.
+        TracingSession tracingSession = new TracingSession(null, APP_FILE_PATH, 123,
+                APP_PACKAGE_NAME, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+        // Mock tracing session to be running.
+        doReturn(true).when(mActiveTrace).isAlive();
+
+        // Add trace to session and session to ProfilingService tracked sessions.
+        tracingSession.setActiveTrace(mActiveTrace);
+        mProfilingService.mTracingSessions.put(
+                (new UUID(KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS)).toString(), tracingSession);
+
+        // Confirm check returns that a trace is running.
+        assertTrue(mProfilingService.areAnyTracesRunning());
+    }
+
+    /** Test that checking if any traces are running works when trace is not running. */
+    @Test
+    public void testAreAnyTracesRunning_False() {
+        mProfilingService.mTracingSessions.clear();
+        assertFalse(mProfilingService.areAnyTracesRunning());
+
+        TracingSession tracingSession = new TracingSession(null, APP_FILE_PATH, 123,
+                APP_PACKAGE_NAME, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+        mProfilingService.mTracingSessions.put(
+                (new UUID(KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS)).toString(), tracingSession);
+        assertFalse(mProfilingService.areAnyTracesRunning());
+    }
+
+    /** Test that request cancel trace does nothing if no trace is running. */
+    @Test
+    public void testRequestCancel_NotRunning() {
+        // Ensure no active tracing sessions tracked.
+        mProfilingService.mTracingSessions.clear();
+        assertFalse(mProfilingService.areAnyTracesRunning());
+
+        // Register callback.
+        ProfilingResultCallback callback = new ProfilingResultCallback();
+        mProfilingService.registerResultsCallback(callback);
+
+        // Request cancellation.
+        mProfilingService.requestCancel(KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+        // Confirm callback was not triggerd with a result because there was no trace to stop.
+        assertFalse(callback.mResultSent);
+    }
+
+    /** Confirm that all fields returned by callback match expectation. */
+    private void confirmResultCallback(ProfilingResultCallback callback, String resultFile,
+            long keyMostSigBits, long keyLeastSigBits, int status, String tag,
+            boolean errorExpected) {
+        assertEquals(resultFile, callback.mResultFile);
+        assertEquals(keyMostSigBits, callback.mKeyMostSigBits);
+        assertEquals(keyLeastSigBits, callback.mKeyLeastSigBits);
+        assertEquals(status, callback.mStatus);
+        assertEquals(tag, callback.mTag);
+        if (errorExpected) {
+            assertNotNull(callback.mError);
+        } else {
+            assertNull(callback.mError);
+        }
+    }
+
+    public static class ProfilingResultCallback extends IProfilingResultCallback.Stub {
+        boolean mResultSent = false;
+        boolean mFileRequested = false;
+        public String mResultFile;
+        public long mKeyMostSigBits;
+        public long mKeyLeastSigBits;
+        public int mStatus;
+        public String mTag;
+        public String mError;
+        @Override
+        public void sendResult(String resultFile, long keyMostSigBits,
+                long keyLeastSigBits, int status, String tag, String error) {
+            mResultSent = true;
+            mResultFile = resultFile;
+            mKeyMostSigBits = keyMostSigBits;
+            mKeyLeastSigBits = keyLeastSigBits;
+            mStatus = status;
+            mTag = tag;
+            mError = error;
+        }
+        @Override
+        public ParcelFileDescriptor generateFile(String filePathAbsolute, String fileName) {
+            mFileRequested = true;
+            return null;
+        }
     }
 }
 
diff --git a/tests/cts/src/android/profiling/cts/ProfilingTestUtils.java b/tests/cts/src/android/profiling/cts/ProfilingTestUtils.java
index e6297f0..e17e2d4 100644
--- a/tests/cts/src/android/profiling/cts/ProfilingTestUtils.java
+++ b/tests/cts/src/android/profiling/cts/ProfilingTestUtils.java
@@ -21,7 +21,6 @@
 import android.os.OutcomeReceiver;
 import android.os.ParcelUuid;
 import android.os.ProfilingRequest;
-import android.os.ProfilingRequest.SystemTrace;
 import android.os.ProfilingResult;
 import android.os.RemoteException;
 import android.util.Log;
@@ -38,11 +37,59 @@
     }
 
     public static byte[] getSystemTraceProfilingRequest() {
-        SystemTrace systemTrace = ProfilingRequest.SystemTrace.newBuilder().build();
-        ProfilingRequest.Config profilingRequestConfig =
-                ProfilingRequest.Config.newBuilder().setSystemTrace(systemTrace).build();
-        ProfilingRequest profilingRequest =
-                ProfilingRequest.newBuilder().setConfig(profilingRequestConfig).build();
-        return profilingRequest.toByteArray();
+        // Create system trace proto object.
+        ProfilingRequest.SystemTrace systemTrace
+                = ProfilingRequest.SystemTrace.newBuilder().build();
+
+        // Create config proto object with only system trace type set.
+        ProfilingRequest.Config config
+                = ProfilingRequest.Config.newBuilder().setSystemTrace(systemTrace).build();
+
+        // Create profiling request object with config and convert to byte array.
+        return getProfilingRequestFromConfig(config);
+    }
+
+    public static byte[] getJavaHeapDumpProfilingRequest() {
+        // Create jave heap dump proto object.
+        ProfilingRequest.JavaHeapDump javaHeapDump
+                = ProfilingRequest.JavaHeapDump.newBuilder().build();
+
+        // Create config proto object with only java heap dump type set.
+        ProfilingRequest.Config config
+                = ProfilingRequest.Config.newBuilder().setJavaHeapDump(javaHeapDump).build();
+
+        // Create profiling request object with config and convert to byte array.
+        return getProfilingRequestFromConfig(config);
+    }
+
+    public static byte[] getHeapProfileProfilingRequest() {
+        // Create heap profile proto object.
+        ProfilingRequest.HeapProfile heapProfile
+                = ProfilingRequest.HeapProfile.newBuilder().build();
+
+
+        // Create config proto object with only heap profile type set.
+        ProfilingRequest.Config config
+                = ProfilingRequest.Config.newBuilder().setHeapProfile(heapProfile).build();
+
+        // Create profiling request object with config and convert to byte array.
+        return getProfilingRequestFromConfig(config);
+    }
+
+    public static byte[] getStackSamplingProfilingRequest() {
+        // Create stack sampling proto object.
+        ProfilingRequest.StackSampling stackSampling
+                = ProfilingRequest.StackSampling.newBuilder().build();
+
+        // Create config proto object with only stack sampling type set.
+        ProfilingRequest.Config config
+                = ProfilingRequest.Config.newBuilder().setStackSampling(stackSampling).build();
+
+        // Create profiling request object with config and convert to byte array.
+        return getProfilingRequestFromConfig(config);
+    }
+
+    private static byte[] getProfilingRequestFromConfig(ProfilingRequest.Config config) {
+        return ProfilingRequest.newBuilder().setConfig(config).build().toByteArray();
     }
 }
\ No newline at end of file