Add performance test for OomAdjuster

Bug: 140254153
Test: atest -c ActivityManagerPerfTests:OomAdjPerfTest#testOomAdj
Change-Id: Id12667c71300e6fe4dc063c83807834bbdb5e62a
diff --git a/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
index c096cd2..4ed3b4e 100644
--- a/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
@@ -95,7 +95,7 @@
 
         mTraceMarkParser.forAllSlices((key, slices) -> {
             for (TraceMarkSlice slice : slices) {
-                state.addExtraResult(key, (long) (slice.getDurarionInSeconds() * NANOS_PER_S));
+                state.addExtraResult(key, (long) (slice.getDurationInSeconds() * NANOS_PER_S));
             }
         });
 
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
index b075239..fe2b1f6 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
@@ -23,11 +23,15 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -140,6 +144,8 @@
     /** @see #addExtraResult(String, long) */
     private ArrayMap<String, ArrayList<Long>> mExtraResults;
 
+    private final List<Long> mTmpDurations = Arrays.asList(0L);
+
     // Statistics. These values will be filled when the benchmark has finished.
     // The computation needs double precision, but long int is fine for final reporting.
     private Stats mStats;
@@ -188,14 +194,25 @@
         if (duration < 0) {
             throw new RuntimeException("duration is negative: " + duration);
         }
+        mTmpDurations.set(0, duration);
+        return keepRunning(mTmpDurations);
+    }
+
+    /**
+     * Similar to the {@link #keepRunning(long)} but accepts a list of durations
+     */
+    public boolean keepRunning(List<Long> durations) {
         switch (mState) {
             case NOT_STARTED:
                 mState = WARMUP;
                 mWarmupStartTime = System.nanoTime();
                 return true;
             case WARMUP: {
+                if (ArrayUtils.isEmpty(durations)) {
+                    return true;
+                }
                 final long timeSinceStartingWarmup = System.nanoTime() - mWarmupStartTime;
-                ++mWarmupIterations;
+                mWarmupIterations += durations.size();
                 if (mWarmupIterations >= WARMUP_MIN_ITERATIONS
                         && timeSinceStartingWarmup >= mWarmupDurationNs) {
                     beginBenchmark(timeSinceStartingWarmup, mWarmupIterations);
@@ -203,7 +220,10 @@
                 return true;
             }
             case RUNNING: {
-                mResults.add(duration);
+                if (ArrayUtils.isEmpty(durations)) {
+                    return true;
+                }
+                mResults.addAll(durations);
                 final boolean keepRunning = mResults.size() < mMaxIterations;
                 if (!keepRunning) {
                     mStats = new Stats(mResults);
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
index 1afed3a..b15b6f6 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
@@ -40,6 +40,8 @@
 
     private final Predicate<TraceMarkLine> mTraceLineFilter;
 
+    private static final long MICROS_PER_SECOND = 1000L * 1000L;
+
     public TraceMarkParser(Predicate<TraceMarkLine> traceLineFilter) {
         mTraceLineFilter = traceLineFilter;
     }
@@ -116,13 +118,19 @@
         }
     }
 
+    public void reset() {
+        mSlicesMap.clear();
+        mDepthMap.clear();
+        mPendingStarts.clear();
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
         forAllSlices((key, slices) -> {
             double totalMs = 0;
             for (TraceMarkSlice s : slices) {
-                totalMs += s.getDurarionInSeconds() * 1000;
+                totalMs += s.getDurationInSeconds() * 1000;
             }
             sb.append(key).append(" count=").append(slices.size()).append(" avg=")
                     .append(totalMs / slices.size()).append("ms\n");
@@ -134,6 +142,10 @@
         return sb.toString();
     }
 
+    static double microsecondToSeconds(long ms) {
+        return (ms * 1.0d) / MICROS_PER_SECOND;
+    }
+
     public static class TraceMarkSlice {
         public final TraceMarkLine begin;
         public final TraceMarkLine end;
@@ -143,7 +155,11 @@
             this.end = end;
         }
 
-        public double getDurarionInSeconds() {
+        public double getDurationInSeconds() {
+            return microsecondToSeconds(end.timestamp - begin.timestamp);
+        }
+
+        public long getDurationInMicroseconds() {
             return end.timestamp - begin.timestamp;
         }
     }
@@ -164,7 +180,7 @@
         static final char SYNC_END = 'E';
 
         public final String taskPid;
-        public final double timestamp;
+        public final long timestamp; // in microseconds
         public final String name;
         public final boolean isAsync;
         public final boolean isBegin;
@@ -179,7 +195,7 @@
             if (timeBegin < 0) {
                 throw new IllegalArgumentException("Timestamp start not found");
             }
-            timestamp = Double.parseDouble(rawLine.substring(timeBegin, timeEnd));
+            timestamp = parseMicroseconds(rawLine.substring(timeBegin, timeEnd));
             isAsync = type == ASYNC_START || type == ASYNC_FINISH;
             isBegin = type == ASYNC_START || type == SYNC_BEGIN;
 
@@ -223,9 +239,29 @@
             return null;
         }
 
+        /**
+         * Parse the timestamp from atrace output, the format will be like:
+         * 84962.920719  where the decimal part will be always exactly 6 digits.
+         * ^^^^^ ^^^^^^
+         * |     |
+         * sec   microsec
+         */
+        static long parseMicroseconds(String line) {
+            int end = line.length();
+            long t = 0;
+            for (int i = 0; i < end; i++) {
+                char c = line.charAt(i);
+                if (c >= '0' && c <= '9') {
+                    t = t * 10 + (c - '0');
+                }
+            }
+            return t;
+        }
+
         @Override
         public String toString() {
-            return "TraceMarkLine{pid=" + taskPid + " time=" + timestamp + " name=" + name
+            return "TraceMarkLine{pid=" + taskPid + " time="
+                    + microsecondToSeconds(timestamp) + " name=" + name
                     + " async=" + isAsync + " begin=" + isBegin + "}";
         }
     }
diff --git a/tests/ActivityManagerPerfTests/stub-app/Android.bp b/tests/ActivityManagerPerfTests/stub-app/Android.bp
new file mode 100644
index 0000000..a3c1f5b
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/Android.bp
@@ -0,0 +1,68 @@
+// Copyright (C) 2019 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.
+
+android_test_helper_app {
+    name: "ActivityManagerPerfTestsStubApp1",
+    static_libs: ["ActivityManagerPerfTestsUtils"],
+    srcs: [
+        "src/**/*.java",
+    ],
+    resource_dirs: [
+        "app1/res",
+        "res",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: [
+        "--rename-manifest-package com.android.stubs.am1",
+        "--auto-add-overlay",
+    ],
+}
+
+android_test_helper_app {
+    name: "ActivityManagerPerfTestsStubApp2",
+    static_libs: ["ActivityManagerPerfTestsUtils"],
+    srcs: [
+        "src/**/*.java",
+    ],
+    resource_dirs: [
+        "app2/res",
+        "res",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: [
+        "--rename-manifest-package com.android.stubs.am2",
+        "--auto-add-overlay",
+    ],
+}
+
+android_test_helper_app {
+    name: "ActivityManagerPerfTestsStubApp3",
+    static_libs: ["ActivityManagerPerfTestsUtils"],
+    srcs: [
+        "src/**/*.java",
+    ],
+    resource_dirs: [
+        "app3/res",
+        "res",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: [
+        "--rename-manifest-package com.android.stubs.am3",
+        "--auto-add-overlay",
+    ],
+}
+
diff --git a/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml b/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml
new file mode 100644
index 0000000..a57f64c
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.stubs.am">
+
+    <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
+    <application android:label="Android TestCase" >
+        <provider
+                android:authorities="@string/authority"
+                android:name=".TestContentProvider"
+                android:exported="true" />
+        <receiver
+                android:name=".TestBroadcastReceiver"
+                android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.stubs.am.ACTION_BROADCAST_TEST" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </receiver>
+        <service
+                android:name=".InitService"
+                android:exported="true" />
+        <service
+                android:name=".TestService"
+                android:exported="true" />
+        <activity
+                android:name=".TestActivity"
+                android:excludeFromRecents="true"
+                android:turnScreenOn="true"
+                android:launchMode="singleTask">
+            <intent-filter>
+                <action android:name="com.android.stubs.am.ACTION_START_TEST_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
+
diff --git a/tests/ActivityManagerPerfTests/stub-app/app1/res/values/config.xml b/tests/ActivityManagerPerfTests/stub-app/app1/res/values/config.xml
new file mode 100644
index 0000000..667472d
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/app1/res/values/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="authority" translatable="false">com.android.stubs.am1.testapp</string>
+</resources>
diff --git a/tests/ActivityManagerPerfTests/stub-app/app2/res/values/config.xml b/tests/ActivityManagerPerfTests/stub-app/app2/res/values/config.xml
new file mode 100644
index 0000000..0852735
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/app2/res/values/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="authority" translatable="false">com.android.stubs.am2.testapp</string>
+</resources>
diff --git a/tests/ActivityManagerPerfTests/stub-app/app3/res/values/config.xml b/tests/ActivityManagerPerfTests/stub-app/app3/res/values/config.xml
new file mode 100644
index 0000000..6895d72
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/app3/res/values/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="authority" translatable="false">com.android.stubs.am3.testapp</string>
+</resources>
diff --git a/tests/ActivityManagerPerfTests/stub-app/res/layout/activity_content.xml b/tests/ActivityManagerPerfTests/stub-app/res/layout/activity_content.xml
new file mode 100644
index 0000000..f79f006
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/res/layout/activity_content.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/content"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"/>
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/InitService.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/InitService.java
new file mode 100644
index 0000000..18fdc44
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/InitService.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2019 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 com.android.stubs.am;
+
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_ACQUIRE_CONTENT_PROVIDER;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_BIND_SERVICE;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_RELEASE_CONTENT_PROVIDER;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_SEND_BROADCAST;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_START_ACTIVITY;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_STOP_ACTIVITY;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_UNBIND_SERVICE;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+import com.android.frameworks.perftests.am.util.ICommandReceiver;
+
+public class InitService extends Service {
+    private static final String TAG = "InitService";
+    public static final boolean VERBOSE = false;
+
+    private static class Stub extends ICommandReceiver.Stub {
+        private final Context mContext;
+        private final Messenger mCallback;
+        private final Handler mHandler;
+        private final Messenger mMessenger;
+        final ArrayMap<String, MyServiceConnection> mServices =
+                new ArrayMap<String, MyServiceConnection>();
+        final ArrayMap<Uri, IContentProvider> mProviders =
+                new ArrayMap<Uri, IContentProvider>();
+
+        Stub(Context context, Messenger callback) {
+            mContext = context;
+            mCallback = callback;
+            HandlerThread thread = new HandlerThread("result handler");
+            thread.start();
+            mHandler = new H(thread.getLooper());
+            mMessenger = new Messenger(mHandler);
+        }
+
+        private class H extends Handler {
+            H(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == Constants.MSG_DEFAULT) {
+                    if (VERBOSE) {
+                        Log.i(TAG, "H: received seq=" + msg.arg1 + ", result=" + msg.arg2);
+                    }
+                    sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, msg.arg1, msg.arg2, null);
+                } else if (msg.what == Constants.MSG_UNBIND_DONE) {
+                    if (VERBOSE) {
+                        Log.i(TAG, "H: received unbind=" + msg.obj);
+                    }
+                    synchronized (InitService.sStub) {
+                        Bundle b = (Bundle) msg.obj;
+                        String pkg = b.getString(Constants.EXTRA_SOURCE_PACKAGE, "");
+                        MyServiceConnection c = mServices.remove(pkg);
+                        if (c != null) {
+                            sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, c.mSeq,
+                                    Constants.RESULT_NO_ERROR, null);
+                        }
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void sendCommand(int command, int seq, String sourcePackage, String targetPackage,
+                int flags, Bundle bundle) {
+            if (VERBOSE) {
+                Log.i(TAG, "Received command=" + command + ", seq=" + seq + ", from="
+                        + sourcePackage + ", to=" + targetPackage + ", flags=" + flags);
+            }
+            switch (command) {
+                case COMMAND_BIND_SERVICE:
+                    handleBindService(seq, targetPackage, flags, bundle);
+                    break;
+                case COMMAND_UNBIND_SERVICE:
+                    handleUnbindService(seq, targetPackage);
+                    break;
+                case COMMAND_ACQUIRE_CONTENT_PROVIDER:
+                    acquireProvider(seq, bundle.getParcelable(Constants.EXTRA_URI));
+                    break;
+                case COMMAND_RELEASE_CONTENT_PROVIDER:
+                    releaseProvider(seq, bundle.getParcelable(Constants.EXTRA_URI));
+                    break;
+                case COMMAND_SEND_BROADCAST:
+                    sendBroadcast(seq, targetPackage);
+                    break;
+                case COMMAND_START_ACTIVITY:
+                    startActivity(seq, targetPackage);
+                    break;
+                case COMMAND_STOP_ACTIVITY:
+                    stopActivity(seq, targetPackage);
+                    break;
+            }
+        }
+
+        private void handleBindService(int seq, String targetPackage, int flags, Bundle bundle) {
+            Intent intent = new Intent();
+            intent.setClassName(targetPackage, "com.android.stubs.am.TestService");
+            intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, mMessenger);
+            if (bundle != null) {
+                intent.putExtras(bundle);
+            }
+            synchronized (this) {
+                if (!mServices.containsKey(targetPackage)) {
+                    MyServiceConnection c = new MyServiceConnection(mCallback);
+                    c.mSeq = seq;
+                    if (!mContext.bindService(intent, c, flags)) {
+                        Log.e(TAG, "Unable to bind to service in " + targetPackage);
+                        sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+                                Constants.RESULT_ERROR, null);
+                    } else {
+                        if (VERBOSE) {
+                            Log.i(TAG, "Bind to service " + intent);
+                        }
+                        mServices.put(targetPackage, c);
+                    }
+                } else {
+                    sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+                            Constants.RESULT_NO_ERROR, null);
+                }
+            }
+        }
+
+        private void handleUnbindService(int seq, String target) {
+            MyServiceConnection c = null;
+            synchronized (this) {
+                c = mServices.get(target);
+            }
+            if (c != null) {
+                c.mSeq = seq;
+                mContext.unbindService(c);
+            }
+        }
+
+        private void acquireProvider(int seq, Uri uri) {
+            ContentResolver resolver = mContext.getContentResolver();
+            IContentProvider provider = resolver.acquireProvider(uri);
+            if (provider != null) {
+                synchronized (mProviders) {
+                    mProviders.put(uri, provider);
+                }
+                sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+                        Constants.RESULT_NO_ERROR, null);
+            } else {
+                sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+                        Constants.RESULT_ERROR, null);
+            }
+        }
+
+        private void releaseProvider(int seq, Uri uri) {
+            ContentResolver resolver = mContext.getContentResolver();
+            IContentProvider provider;
+            synchronized (mProviders) {
+                provider = mProviders.get(uri);
+            }
+            if (provider != null) {
+                resolver.releaseProvider(provider);
+                synchronized (mProviders) {
+                    mProviders.remove(uri);
+                }
+            }
+            sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+                    Constants.RESULT_NO_ERROR, null);
+        }
+
+        private void sendBroadcast(final int seq, String targetPackage) {
+            Intent intent = new Intent(Constants.STUB_ACTION_BROADCAST);
+            intent.setPackage(targetPackage);
+            mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+                            Constants.RESULT_NO_ERROR, null);
+                }
+            }, null, 0, null, null);
+        }
+
+        private void startActivity(int seq, String targetPackage) {
+            Intent intent = new Intent(Constants.STUB_ACTION_ACTIVITY);
+            intent.setClassName(targetPackage, "com.android.stubs.am.TestActivity");
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            intent.putExtra(Constants.EXTRA_ARG1, seq);
+            intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, mMessenger);
+            mContext.startActivity(intent);
+        }
+
+        private void stopActivity(int seq, String targetPackage) {
+            Intent intent = new Intent(Constants.STUB_ACTION_ACTIVITY);
+            intent.setClassName(targetPackage, "com.android.stubs.am.TestActivity");
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            intent.putExtra(Constants.EXTRA_REQ_FINISH_ACTIVITY, true);
+            intent.putExtra(Constants.EXTRA_ARG1, seq);
+            intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, mMessenger);
+            mContext.startActivity(intent);
+        }
+    };
+
+    private static void sendResult(Messenger callback, int what, int seq, int result, Object obj) {
+        Message msg = Message.obtain();
+        msg.what = what;
+        msg.arg1 = seq;
+        msg.arg2 = result;
+        msg.obj = obj;
+        try {
+            if (VERBOSE) {
+                Log.i(TAG, "Sending result seq=" + seq + ", result=" + result);
+            }
+            callback.send(msg);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in sending result back", e);
+        }
+        msg.recycle();
+    }
+
+    private static class MyServiceConnection implements ServiceConnection {
+        private Messenger mCallback;
+        int mSeq;
+
+        MyServiceConnection(Messenger callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, mSeq,
+                    Constants.RESULT_NO_ERROR, null);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized (sStub) {
+                MyServiceConnection c = sStub.mServices.remove(name.getPackageName());
+                if (c != null) {
+                    sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, c.mSeq,
+                            Constants.RESULT_NO_ERROR, null);
+                }
+            }
+        }
+    }
+
+    private static Stub sStub = null;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new Binder();
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Messenger callback = intent.getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK);
+        if (sStub == null) {
+            sStub = new Stub(getApplicationContext(), callback);
+        }
+
+        Bundle extras = new Bundle();
+        extras.putString(Constants.EXTRA_SOURCE_PACKAGE, getPackageName());
+        extras.putBinder(Constants.EXTRA_RECEIVER_CALLBACK, sStub);
+        sendResult(callback, Constants.REPLY_PACKAGE_START_RESULT,
+                intent.getIntExtra(Constants.EXTRA_SEQ, -1), 0, extras);
+        return START_NOT_STICKY;
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestActivity.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestActivity.java
new file mode 100644
index 0000000..f7ea356
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestActivity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 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 com.android.stubs.am;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+
+public class TestActivity extends Activity {
+    private static final String TAG = "TestActivity";
+    private static final boolean VERBOSE = InitService.VERBOSE;
+
+    private Messenger mResultTo;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        if (VERBOSE) {
+            Log.i(TAG, getPackageName() + " onCreate()");
+        }
+        setContentView(R.layout.activity_content);
+        mResultTo = getIntent().getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK);
+    }
+
+    @Override
+    public void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        setIntent(intent);
+        mResultTo = intent.getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK);
+        if (intent.getBooleanExtra(Constants.EXTRA_REQ_FINISH_ACTIVITY, false)) {
+            if (VERBOSE) {
+                Log.i(TAG, getPackageName() + " finishing activity");
+            }
+            finish();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (VERBOSE) {
+            Log.i(TAG, getPackageName() + " onResume()");
+        }
+        sendResult(Constants.RESULT_NO_ERROR);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (VERBOSE) {
+            Log.i(TAG, getPackageName() + " onDestroy()");
+        }
+        sendResult(Constants.RESULT_NO_ERROR);
+    }
+
+    private void sendResult(int result) {
+        Message msg = Message.obtain();
+        msg.arg1 = getIntent().getIntExtra(Constants.EXTRA_ARG1, -1);
+        msg.arg2 = result;
+        try {
+            mResultTo.send(msg);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in sending result back", e);
+        }
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestBroadcastReceiver.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestBroadcastReceiver.java
new file mode 100644
index 0000000..36c7a0a
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestBroadcastReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 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 com.android.stubs.am;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class TestBroadcastReceiver extends BroadcastReceiver {
+    private static final String TAG = "TestBroadcastReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, context.getPackageName() + " received broadcast: " + intent);
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestContentProvider.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestContentProvider.java
new file mode 100644
index 0000000..4fdbf1f
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestContentProvider.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 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 com.android.stubs.am;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.Log;
+
+public class TestContentProvider extends ContentProvider {
+    private static final String TAG = "TestContentProvider";
+    private static final boolean VERBOSE = InitService.VERBOSE;
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public boolean onCreate() {
+        if (VERBOSE) {
+            Log.i(TAG, getContext().getPackageName() + " onCreate()");
+        }
+        return false;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestService.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestService.java
new file mode 100644
index 0000000..ba220e0
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestService.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 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 com.android.stubs.am;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+
+public class TestService extends Service {
+    private static final String TAG = "TestService";
+    private static final boolean VERBOSE = InitService.VERBOSE;
+
+    private Binder mStub = new Binder();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (VERBOSE) {
+            Log.i(TAG, getPackageName() + " onBind()");
+        }
+        return mStub;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (VERBOSE) {
+            Log.i(TAG, getPackageName() + " onStartCommand()");
+        }
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        if (VERBOSE) {
+            Log.i(TAG, getPackageName() + " onUnbind()");
+        }
+        Messenger messenger = intent.getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK);
+        Message msg = Message.obtain();
+        msg.what = Constants.MSG_UNBIND_DONE;
+        Bundle b = new Bundle();
+        b.putString(Constants.EXTRA_SOURCE_PACKAGE, getPackageName());
+        msg.obj = b;
+        try {
+            messenger.send(msg);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in sending result back", e);
+        }
+        return false;
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/AndroidTest.xml b/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
index 76c40b2..475bb82 100644
--- a/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
+++ b/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
@@ -17,6 +17,9 @@
     <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
         <option name="test-file-name" value="ActivityManagerPerfTests.apk"/>
         <option name="test-file-name" value="ActivityManagerPerfTestsTestApp.apk"/>
+        <option name="test-file-name" value="ActivityManagerPerfTestsStubApp1.apk"/>
+        <option name="test-file-name" value="ActivityManagerPerfTestsStubApp2.apk"/>
+        <option name="test-file-name" value="ActivityManagerPerfTestsStubApp3.apk"/>
         <option name="cleanup-apks" value="true"/>
     </target_preparer>
 
@@ -26,4 +29,4 @@
         <option name="package" value="com.android.frameworks.perftests.amtests"/>
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
     </test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/OomAdjPerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/OomAdjPerfTest.java
new file mode 100644
index 0000000..1d3ff06
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/OomAdjPerfTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2019 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 com.android.frameworks.perftests.am.tests;
+
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.perftests.utils.TraceMarkParser;
+import android.perftests.utils.TraceMarkParser.TraceMarkLine;
+import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+
+import com.android.frameworks.perftests.am.util.AtraceUtils;
+import com.android.frameworks.perftests.am.util.TargetPackageUtils;
+import com.android.frameworks.perftests.am.util.Utils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This benchmark test basically manipulates 3 test packages, let them bind to
+ * each other, send broadcast to each other, etc. All of these actions essentially
+ * triggers OomAdjuster to update the oom_adj scores and proc state of them.
+ * In the meanwhile it'll also monitor the atrace output, extract duration between
+ * the start and exit entries of the updateOomAdjLocked, include each of them
+ * into the stats; when there are enough samples in the stats, the test will
+ * stop and output the mean/stddev time spent on the updateOomAdjLocked.
+ */
+@RunWith(JUnit4.class)
+@LargeTest
+public final class OomAdjPerfTest {
+    private static final String TAG = "OomAdjPerfTest";
+    private static final boolean VERBOSE = true;
+
+    private static final String STUB_PACKAGE1_NAME = "com.android.stubs.am1";
+    private static final String STUB_PACKAGE2_NAME = "com.android.stubs.am2";
+    private static final String STUB_PACKAGE3_NAME = "com.android.stubs.am3";
+
+    private static final Uri STUB_PACKAGE1_URI = new Uri.Builder().scheme(
+            ContentResolver.SCHEME_CONTENT).authority("com.android.stubs.am1.testapp").build();
+    private static final Uri STUB_PACKAGE2_URI = new Uri.Builder().scheme(
+            ContentResolver.SCHEME_CONTENT).authority("com.android.stubs.am2.testapp").build();
+    private static final Uri STUB_PACKAGE3_URI = new Uri.Builder().scheme(
+            ContentResolver.SCHEME_CONTENT).authority("com.android.stubs.am3.testapp").build();
+    private static final long NANOS_PER_MICROSECOND = 1000L;
+
+    private static final String ATRACE_CATEGORY = "am";
+    private static final String ATRACE_OOMADJ_PREFIX = "updateOomAdj_";
+
+    @Rule
+    public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
+    private TraceMarkParser mTraceMarkParser = new TraceMarkParser(this::shouldFilterTraceLine);
+    private final ArrayList<Long> mDurations = new ArrayList<Long>();
+    private Context mContext;
+    private HandlerThread mHandlerThread;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mHandlerThread = new HandlerThread("command receiver");
+        mHandlerThread.start();
+        TargetPackageUtils.initCommandResultReceiver(mHandlerThread.getLooper());
+
+        Utils.runShellCommand("cmd deviceidle whitelist +" + STUB_PACKAGE1_NAME);
+        Utils.runShellCommand("cmd deviceidle whitelist +" + STUB_PACKAGE2_NAME);
+        Utils.runShellCommand("cmd deviceidle whitelist +" + STUB_PACKAGE3_NAME);
+        TargetPackageUtils.startStubPackage(mContext, STUB_PACKAGE1_NAME);
+        TargetPackageUtils.startStubPackage(mContext, STUB_PACKAGE2_NAME);
+        TargetPackageUtils.startStubPackage(mContext, STUB_PACKAGE3_NAME);
+    }
+
+    @After
+    public void tearDown() {
+        TargetPackageUtils.stopStubPackage(mContext, STUB_PACKAGE1_NAME);
+        TargetPackageUtils.stopStubPackage(mContext, STUB_PACKAGE2_NAME);
+        TargetPackageUtils.stopStubPackage(mContext, STUB_PACKAGE3_NAME);
+        Utils.runShellCommand("cmd deviceidle whitelist -" + STUB_PACKAGE1_NAME);
+        Utils.runShellCommand("cmd deviceidle whitelist -" + STUB_PACKAGE2_NAME);
+        Utils.runShellCommand("cmd deviceidle whitelist -" + STUB_PACKAGE3_NAME);
+        mHandlerThread.quitSafely();
+    }
+
+    @Test
+    public void testOomAdj() {
+        final AtraceUtils atraceUtils = AtraceUtils.getInstance(
+                InstrumentationRegistry.getInstrumentation());
+        final ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
+        atraceUtils.startTrace(ATRACE_CATEGORY);
+        while (state.keepRunning(mDurations)) {
+            runCUJWithOomComputationOnce();
+
+            // Now kick off the trace dump
+            mDurations.clear();
+            atraceUtils.performDump(mTraceMarkParser, this::handleTraceMarkSlices);
+        }
+        atraceUtils.stopTrace();
+    }
+
+    private boolean shouldFilterTraceLine(final TraceMarkLine line) {
+        return line.name.startsWith(ATRACE_OOMADJ_PREFIX);
+    }
+
+    private void handleTraceMarkSlices(String key, List<TraceMarkSlice> slices) {
+        for (TraceMarkSlice slice: slices) {
+            mDurations.add(slice.getDurationInMicroseconds() * NANOS_PER_MICROSECOND);
+        }
+    }
+
+    /**
+     * This tries to mimic a user journey, involes multiple activity/service starts/stop,
+     * the time spent on oom adj computation would be different between all these samples,
+     * but with enough samples, we'll be able to know the overall distribution of the time
+     * spent on it.
+     */
+    private void runCUJWithOomComputationOnce() {
+        // Start activity from package1
+        TargetPackageUtils.startActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+        // Start activity from package2
+        TargetPackageUtils.startActivity(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME);
+        // Start activity from package3
+        TargetPackageUtils.startActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+
+        // Stop activity in package1
+        TargetPackageUtils.stopActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+        // Stop activity in package2
+        TargetPackageUtils.stopActivity(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME);
+        // Stop activity in package3
+        TargetPackageUtils.stopActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+
+        // Bind from package1 to package2
+        TargetPackageUtils.bindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE);
+        // Acquire content provider from package 1 to package3
+        TargetPackageUtils.acquireProvider(STUB_PACKAGE1_NAME, STUB_PACKAGE3_NAME,
+                STUB_PACKAGE3_URI);
+        // Start activity from package1
+        TargetPackageUtils.startActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+        // Bind from package2 to package3
+        TargetPackageUtils.bindService(STUB_PACKAGE2_NAME, STUB_PACKAGE3_NAME,
+                Context.BIND_AUTO_CREATE);
+        // Unbind from package 1 to package 2
+        TargetPackageUtils.unbindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME, 0);
+        // Stop activity in package1
+        TargetPackageUtils.stopActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+
+        // Send broadcast to all of them
+        TargetPackageUtils.sendBroadcast(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+        TargetPackageUtils.sendBroadcast(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME);
+        TargetPackageUtils.sendBroadcast(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+
+        // Bind from package1 to package2 again
+        TargetPackageUtils.bindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE);
+        // Create a cycle: package3 to package1
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME,
+                Context.BIND_AUTO_CREATE);
+
+        // Send broadcast to all of them again
+        TargetPackageUtils.sendBroadcast(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+        TargetPackageUtils.sendBroadcast(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME);
+        TargetPackageUtils.sendBroadcast(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+        // Start activity in package3
+        TargetPackageUtils.startActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+
+        // Break the cycle: unbind from package3 to package1
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME, 0);
+
+        // Bind from package3 to package1 with waive priority
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME,
+                Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);
+        // Release provider connection
+        TargetPackageUtils.releaseProvider(STUB_PACKAGE1_NAME, STUB_PACKAGE3_NAME,
+                STUB_PACKAGE3_URI);
+        // Unbind from package1 to package2
+        TargetPackageUtils.unbindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME, 0);
+        // Unbind from package2 to packagae3
+        TargetPackageUtils.unbindService(STUB_PACKAGE2_NAME, STUB_PACKAGE3_NAME, 0);
+
+        // Bind from package3 to package2 with BIND_ABOVE_CLIENT
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE | Context.BIND_ABOVE_CLIENT);
+        // Unbind from package3 to packagae2
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+        // Bind from package3 to package2 with BIND_ALLOW_OOM_MANAGEMENT
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_OOM_MANAGEMENT);
+        // Unbind from package3 to packagae2
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+        // Bind from package3 to package2 with BIND_IMPORTANT
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
+        // Unbind from package3 to packagae2
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+        // Bind from package3 to package2 with BIND_NOT_FOREGROUND
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
+        // Unbind from package3 to packagae2
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+        // Bind from package3 to package2 with BIND_NOT_PERCEPTIBLE
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE | Context.BIND_NOT_PERCEPTIBLE);
+        // Unbind from package3 to packagae2
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+        // Stop activity in package3
+        TargetPackageUtils.stopActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+        // Unbind from package3 to package1
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME, 0);
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/AtraceUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/AtraceUtils.java
new file mode 100644
index 0000000..fcccfce
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/AtraceUtils.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 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 com.android.frameworks.perftests.am.util;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.ParcelFileDescriptor;
+import android.perftests.utils.TraceMarkParser;
+import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+// Simplified version of AtraceLogger.
+public class AtraceUtils {
+    private static final String TAG = "AtraceUtils";
+    private static final boolean VERBOSE = true;
+
+    private static final String ATRACE_START = "atrace --async_start -b %d -c %s";
+    private static final String ATRACE_DUMP = "atrace --async_dump";
+    private static final String ATRACE_STOP = "atrace --async_stop";
+    private static final int DEFAULT_ATRACE_BUF_SIZE = 1024;
+
+    private UiAutomation mAutomation;
+    private static AtraceUtils sUtils = null;
+    private boolean mStarted = false;
+
+    private AtraceUtils(Instrumentation instrumentation) {
+        mAutomation = instrumentation.getUiAutomation();
+    }
+
+    public static AtraceUtils getInstance(Instrumentation instrumentation) {
+        if (sUtils == null) {
+            sUtils = new AtraceUtils(instrumentation);
+        }
+        return sUtils;
+    }
+
+    /**
+     * @param categories The list of the categories to trace, separated with space.
+     */
+    public void startTrace(String categories) {
+        synchronized (this) {
+            if (mStarted) {
+                throw new IllegalStateException("atrace already started");
+            }
+            Utils.runShellCommand(String.format(
+                    ATRACE_START, DEFAULT_ATRACE_BUF_SIZE, categories));
+            mStarted = true;
+        }
+    }
+
+    public void stopTrace() {
+        synchronized (this) {
+            mStarted = false;
+            Utils.runShellCommand(ATRACE_STOP);
+        }
+    }
+
+    /**
+     * @param parser The function that can accept the buffer of atrace dump and parse it.
+     * @param handler The parse result handler
+     */
+    public void performDump(TraceMarkParser parser,
+            BiConsumer<String, List<TraceMarkSlice>> handler) {
+        parser.reset();
+        try {
+            if (VERBOSE) {
+                Log.i(TAG, "Collecting atrace dump...");
+            }
+            writeDataToBuf(mAutomation.executeShellCommand(ATRACE_DUMP), parser);
+        } catch (IOException e) {
+            Log.e(TAG, "Error in reading dump", e);
+        }
+        parser.forAllSlices(handler);
+    }
+
+    // The given file descriptor here will be closed by this function
+    private void writeDataToBuf(ParcelFileDescriptor pfDescriptor,
+            TraceMarkParser parser) throws IOException {
+        InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfDescriptor);
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                parser.visit(line);
+            }
+        }
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
index 046dd6b..d7f4d9d 100644
--- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
@@ -22,12 +22,18 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.ResultReceiver;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
 import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
 
 import org.junit.Assert;
 
@@ -36,6 +42,7 @@
 
 public class TargetPackageUtils {
     private static final String TAG = TargetPackageUtils.class.getSimpleName();
+    public static final boolean VERBOSE = true;
 
     public static final String PACKAGE_NAME = "com.android.frameworks.perftests.amteststestapp";
     public static final String ACTIVITY_NAME = PACKAGE_NAME + ".TestActivity";
@@ -48,6 +55,12 @@
     // Cache for test app's uid, so we only have to query it once.
     private static int sTestAppUid = -1;
 
+    private static final ArrayMap<String, ICommandReceiver> sStubPackages =
+            new ArrayMap<String, ICommandReceiver>();
+    private static final ArrayMap<Integer, CountDownLatch> sCommandLatches =
+            new ArrayMap<Integer, CountDownLatch>();
+    private static int sSeqNum = 0;
+
     /**
      * Kills the test package synchronously.
      */
@@ -145,5 +158,160 @@
         }
     }
 
+    private static boolean isUidRunning(int uid) {
+        return !Utils.runShellCommand(String.format("cmd activity get-uid-state %d", uid))
+                .contains("(NONEXISTENT)");
+    }
+
+    public static void startStubPackage(Context context, String pkgName) {
+        stopStubPackage(context, pkgName);
+        try {
+            Pair<Integer, CountDownLatch> pair = obtainLatch();
+            Intent intent = new Intent();
+            intent.setComponent(new ComponentName(pkgName, Constants.STUB_INIT_SERVICE_NAME));
+            intent.putExtra(Constants.EXTRA_SOURCE_PACKAGE, context.getPackageName());
+            intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, sMessenger);
+            intent.putExtra(Constants.EXTRA_SEQ, pair.first);
+            context.startService(intent);
+            Assert.assertTrue("Timeout when waiting for starting package " +  pkgName,
+                    pair.second.await(AWAIT_SERVICE_CONNECT_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void stopStubPackage(Context context, String pkgName) {
+        final PackageManager pm = context.getPackageManager();
+        try {
+            final int uid = pm.getPackageUid(pkgName, 0);
+            if (isUidRunning(uid)) {
+                ActivityManager am = context.getSystemService(ActivityManager.class);
+                am.forceStopPackage(pkgName);
+                while (isUidRunning(uid)) {
+                    SystemClock.sleep(WAIT_TIME_MS);
+                }
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void initCommandResultReceiver(Looper looper) {
+        if (sMessenger == null) {
+            sMessenger = new Messenger(new H(looper));
+        }
+    }
+
+    private static void onPackageStartResult(int seq, Bundle bundle) {
+        ICommandReceiver receiver = ICommandReceiver.Stub.asInterface(
+                bundle.getBinder(Constants.EXTRA_RECEIVER_CALLBACK));
+        String sourcePkg = bundle.getString(Constants.EXTRA_SOURCE_PACKAGE);
+        sStubPackages.put(sourcePkg, receiver);
+        releaseLatch(seq);
+    }
+
+    private static void onCommandResult(int seq, int result) {
+        Assert.assertTrue("Error in command seq " + seq, result == Constants.RESULT_NO_ERROR);
+        releaseLatch(seq);
+    }
+
+    private static Messenger sMessenger = null;
+    private static class H extends Handler {
+        H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case Constants.REPLY_PACKAGE_START_RESULT:
+                    onPackageStartResult(msg.arg1 /* seq */, (Bundle) msg.obj);
+                    break;
+                case Constants.REPLY_COMMAND_RESULT:
+                    onCommandResult(msg.arg1, msg.arg2);
+                    break;
+            }
+        }
+    }
+
+    private static Pair<Integer, CountDownLatch> obtainLatch() {
+        CountDownLatch latch = new CountDownLatch(1);
+        int seq;
+        synchronized (sCommandLatches) {
+            seq = sSeqNum++;
+            sCommandLatches.put(seq, latch);
+        }
+        return new Pair<>(seq, latch);
+    }
+
+    private static void releaseLatch(int seq) {
+        synchronized (sCommandLatches) {
+            CountDownLatch latch = sCommandLatches.get(seq);
+            if (latch != null) {
+                latch.countDown();
+                sCommandLatches.remove(seq);
+            }
+        }
+    }
+
+    public static void sendCommand(int command, String sourcePackage, String targetPackage,
+            int flags, Bundle bundle, boolean waitForResult) {
+        ICommandReceiver receiver = sStubPackages.get(sourcePackage);
+        Assert.assertTrue("Package hasn't been started: " + sourcePackage, receiver != null);
+        try {
+            Pair<Integer, CountDownLatch> pair = null;
+            if (waitForResult) {
+                pair = obtainLatch();
+            }
+            if (VERBOSE) {
+                Log.i(TAG, "Sending command=" + command + ", seq=" + pair.first + ", from="
+                        + sourcePackage + ", to=" + targetPackage + ", flags=" + flags);
+            }
+            receiver.sendCommand(command, pair.first, sourcePackage, targetPackage, flags, bundle);
+            if (waitForResult) {
+                Assert.assertTrue("Timeout when waiting for command " + command + " from "
+                        + sourcePackage + " to " + targetPackage,
+                        pair.second.await(AWAIT_SERVICE_CONNECT_MS, TimeUnit.MILLISECONDS));
+            }
+        } catch (RemoteException | InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void bindService(String sourcePackage, String targetPackage, int flags) {
+        sendCommand(Constants.COMMAND_BIND_SERVICE, sourcePackage, targetPackage, flags, null,
+                true);
+    }
+
+    public static void unbindService(String sourcePackage, String targetPackage, int flags) {
+        sendCommand(Constants.COMMAND_UNBIND_SERVICE, sourcePackage, targetPackage, flags, null,
+                true);
+    }
+
+    public static void acquireProvider(String sourcePackage, String targetPackage, Uri uri) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(Constants.EXTRA_URI, uri);
+        sendCommand(Constants.COMMAND_ACQUIRE_CONTENT_PROVIDER, sourcePackage, targetPackage, 0,
+                bundle, true);
+    }
+
+    public static void releaseProvider(String sourcePackage, String targetPackage, Uri uri) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(Constants.EXTRA_URI, uri);
+        sendCommand(Constants.COMMAND_RELEASE_CONTENT_PROVIDER, sourcePackage, targetPackage, 0,
+                bundle, true);
+    }
+
+    public static void sendBroadcast(String sourcePackage, String targetPackage) {
+        sendCommand(Constants.COMMAND_SEND_BROADCAST, sourcePackage, targetPackage, 0, null, true);
+    }
+
+    public static void startActivity(String sourcePackage, String targetPackage) {
+        sendCommand(Constants.COMMAND_START_ACTIVITY, sourcePackage, targetPackage, 0, null, true);
+    }
+
+    public static void stopActivity(String sourcePackage, String targetPackage) {
+        sendCommand(Constants.COMMAND_STOP_ACTIVITY, sourcePackage, targetPackage, 0, null, true);
+    }
 }
 
diff --git a/tests/ActivityManagerPerfTests/utils/Android.bp b/tests/ActivityManagerPerfTests/utils/Android.bp
index 300b7ea..766c3ac 100644
--- a/tests/ActivityManagerPerfTests/utils/Android.bp
+++ b/tests/ActivityManagerPerfTests/utils/Android.bp
@@ -18,6 +18,7 @@
     srcs: [
         "src/**/*.java",
         "src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl",
+        "src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl",
     ],
     static_libs: [
         "androidx.test.rules",
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
index 9b076c5..8e58665 100644
--- a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
@@ -30,4 +30,33 @@
 
     public static final String EXTRA_RECEIVER_CALLBACK = "receiver_callback_binder";
     public static final String EXTRA_LOOPER_IDLE_CALLBACK = "looper_idle_callback_binder";
+    public static final String EXTRA_SOURCE_PACKAGE = "source_package";
+    public static final String EXTRA_URI = "uri";
+    public static final String EXTRA_REQ_FINISH_ACTIVITY = "req_finish_activity";
+    public static final String EXTRA_SEQ = "seq";
+    public static final String EXTRA_ARG1 = "arg1";
+    public static final String EXTRA_ARG2 = "arg2";
+
+    public static final int RESULT_NO_ERROR = 0;
+    public static final int RESULT_ERROR = 1;
+    public static final String STUB_INIT_SERVICE_NAME = "com.android.stubs.am.InitService";
+
+    public static final int COMMAND_BIND_SERVICE = 1;
+    public static final int COMMAND_UNBIND_SERVICE = 2;
+    public static final int COMMAND_ACQUIRE_CONTENT_PROVIDER = 3;
+    public static final int COMMAND_RELEASE_CONTENT_PROVIDER = 4;
+    public static final int COMMAND_SEND_BROADCAST = 5;
+    public static final int COMMAND_START_ACTIVITY = 6;
+    public static final int COMMAND_STOP_ACTIVITY = 7;
+
+    public static final int MSG_DEFAULT = 0;
+    public static final int MSG_UNBIND_DONE = 1;
+
+    public static final int REPLY_PACKAGE_START_RESULT = 0;
+    public static final int REPLY_COMMAND_RESULT = 1;
+
+    public static final String STUB_ACTION_ACTIVITY =
+            "com.android.stubs.am.ACTION_START_TEST_ACTIVITY";
+    public static final String STUB_ACTION_BROADCAST =
+            "com.android.stubs.am.ACTION_BROADCAST_TEST";
 }
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl
new file mode 100644
index 0000000..59ea761
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 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 com.android.frameworks.perftests.am.util;
+
+import android.os.Bundle;
+
+interface ICommandReceiver {
+    oneway void sendCommand(int command, int seq, String sourcePackage, String targetPackage,
+            int flags, in Bundle bundle);
+}