Introduce ProfcollectForwardingService

Bug: 79161490
Test: boot and observe logcat
Change-Id: I0413c52f08b930b1e42971acc7f50e2e2da52f99
diff --git a/services/Android.bp b/services/Android.bp
index f0144ac..ef52c2a 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -30,6 +30,7 @@
         ":services.midi-sources",
         ":services.net-sources",
         ":services.print-sources",
+        ":services.profcollect-sources",
         ":services.restrictions-sources",
         ":services.startop.iorap-sources",
         ":services.systemcaptions-sources",
@@ -73,6 +74,7 @@
         "services.net",
         "services.people",
         "services.print",
+        "services.profcollect",
         "services.restrictions",
         "services.startop",
         "services.systemcaptions",
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ae6ccda..c11c4b2 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -152,6 +152,7 @@
 import com.android.server.power.PowerManagerService;
 import com.android.server.power.ShutdownThread;
 import com.android.server.power.ThermalManagerService;
+import com.android.server.profcollect.ProfcollectForwardingService;
 import com.android.server.recoverysystem.RecoverySystemService;
 import com.android.server.restrictions.RestrictionsManagerService;
 import com.android.server.role.RoleManagerService;
@@ -1229,6 +1230,12 @@
             mSystemServiceManager.startService(IorapForwardingService.class);
             t.traceEnd();
 
+            if (Build.IS_DEBUGGABLE) {
+                t.traceBegin("ProfcollectForwardingService");
+                mSystemServiceManager.startService(ProfcollectForwardingService.class);
+                t.traceEnd();
+            }
+
             t.traceBegin("SignedConfigService");
             SignedConfigService.registerUpdateReceiver(mSystemContext);
             t.traceEnd();
diff --git a/services/profcollect/Android.bp b/services/profcollect/Android.bp
new file mode 100644
index 0000000..68fba55
--- /dev/null
+++ b/services/profcollect/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2020 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.
+
+filegroup {
+  name: "services.profcollect-javasources",
+  srcs: ["src/**/*.java"],
+  path: "src",
+  visibility: ["//frameworks/base/services"],
+}
+
+filegroup {
+  name: "services.profcollect-sources",
+  srcs: [
+    ":services.profcollect-javasources",
+    ":profcollectd_aidl",
+  ],
+  visibility: ["//frameworks/base/services:__subpackages__"],
+}
+
+java_library_static {
+  name: "services.profcollect",
+  srcs: [":services.profcollect-sources"],
+  libs: ["services.core"],
+}
diff --git a/services/profcollect/OWNERS b/services/profcollect/OWNERS
new file mode 100644
index 0000000..b380e39
--- /dev/null
+++ b/services/profcollect/OWNERS
@@ -0,0 +1,3 @@
+srhines@google.com
+yabinc@google.com
+yikong@google.com
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
new file mode 100644
index 0000000..bc75dcd
--- /dev/null
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -0,0 +1,196 @@
+/**
+ * Copyright (C) 2020 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.server.profcollect;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder.DeathRecipient;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.server.IoThread;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.wm.ActivityMetricsLaunchObserver;
+import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * System-server-local proxy into the {@code IProfcollectd} native service.
+ */
+public final class ProfcollectForwardingService extends SystemService {
+    public static final String LOG_TAG = "ProfcollectForwardingService";
+
+    private IProfCollectd mIProfcollect;
+    private ProfcollectForwardingService mSelfService;
+    private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper());
+
+    public ProfcollectForwardingService(Context context) {
+        super(context);
+
+        if (mSelfService != null) {
+            throw new AssertionError("only one service instance allowed");
+        }
+        mSelfService = this;
+    }
+
+    @Override
+    public void onStart() {
+        Log.i(LOG_TAG, "Profcollect forwarding service start");
+        connectNativeService();
+        if (mIProfcollect == null) {
+            return;
+        }
+        if (serviceHasSupportedTraceProvider()) {
+            registerObservers();
+        }
+    }
+
+    private boolean serviceHasSupportedTraceProvider() {
+        if (mIProfcollect == null) {
+            return false;
+        }
+        try {
+            return !mIProfcollect.GetSupportedProvider().isEmpty();
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, e.getMessage());
+            return false;
+        }
+    }
+
+    private boolean tryConnectNativeService() {
+        if (connectNativeService()) {
+            return true;
+        }
+        // Cannot connect to the native service at this time, retry after a short delay.
+        mHandler.sendEmptyMessageDelayed(ProfcollectdHandler.MESSAGE_BINDER_CONNECT, 5000);
+        return false;
+    }
+
+    private boolean connectNativeService() {
+        try {
+            IProfCollectd profcollectd =
+                    IProfCollectd.Stub.asInterface(
+                            ServiceManager.getServiceOrThrow("profcollectd"));
+            profcollectd.asBinder().linkToDeath(new ProfcollectdDeathRecipient(), /*flags*/0);
+            mIProfcollect = profcollectd;
+            return true;
+        } catch (ServiceManager.ServiceNotFoundException | RemoteException e) {
+            Log.w(LOG_TAG, "Failed to connect profcollectd binder service.");
+            return false;
+        }
+    }
+
+    private class ProfcollectdHandler extends Handler {
+        public ProfcollectdHandler(Looper looper) {
+            super(looper);
+        }
+
+        public static final int MESSAGE_BINDER_CONNECT = 0;
+
+        @Override
+        public void handleMessage(android.os.Message message) {
+            switch (message.what) {
+                case MESSAGE_BINDER_CONNECT:
+                    connectNativeService();
+                    break;
+                default:
+                    throw new AssertionError("Unknown message: " + message.toString());
+            }
+        }
+    }
+
+    private class ProfcollectdDeathRecipient implements DeathRecipient {
+        @Override
+        public void binderDied() {
+            Log.w(LOG_TAG, "profcollectd has died");
+
+            mIProfcollect = null;
+            tryConnectNativeService();
+        }
+    }
+
+    // Event observers
+    private void registerObservers() {
+        registerAppLaunchObserver();
+    }
+
+    private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
+    private void registerAppLaunchObserver() {
+        ActivityTaskManagerInternal atmInternal =
+                LocalServices.getService(ActivityTaskManagerInternal.class);
+        ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
+                atmInternal.getLaunchObserverRegistry();
+        launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
+    }
+
+    private void traceOnAppStart(String packageName) {
+        if (mIProfcollect == null) {
+            return;
+        }
+
+        // Sample for a fraction of app launches.
+        int traceFrequency = SystemProperties.getInt("profcollectd.applaunch_trace_freq", 2);
+        int randomNum = ThreadLocalRandom.current().nextInt(100);
+        if (randomNum < traceFrequency) {
+            try {
+                Log.i(LOG_TAG, "Tracing on app launch event: " + packageName);
+                mIProfcollect.TraceOnce("applaunch");
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, e.getMessage());
+            }
+        }
+    }
+
+    private class AppLaunchObserver implements ActivityMetricsLaunchObserver {
+        @Override
+        public void onIntentStarted(Intent intent, long timestampNanos) {
+            traceOnAppStart(intent.getPackage());
+        }
+
+        @Override
+        public void onIntentFailed() {
+            // Ignored
+        }
+
+        @Override
+        public void onActivityLaunched(byte[] activity, int temperature) {
+            // Ignored
+        }
+
+        @Override
+        public void onActivityLaunchCancelled(byte[] abortingActivity) {
+            // Ignored
+        }
+
+        @Override
+        public void onActivityLaunchFinished(byte[] finalActivity, long timestampNanos) {
+            // Ignored
+        }
+
+        @Override
+        public void onReportFullyDrawn(byte[] activity, long timestampNanos) {
+            // Ignored
+        }
+    }
+}