wm: Add ActivityMetricsLaunchObserverRegistry

Public(@hide)-visibility app sequence launch events.

Anything in system_server can use this new API to subscribe to
the ActivityMetricsLaunchObserver from the ActivityTaskManagerInternal
LocalService.

Also change AMLO to expose ActivityRecordProto as a byte[]; the previous
parameter was ActivityRecord, which is package-private.

Also add systrace for measuring performance of the AMLO callbacks
(seems to be about ~250us on a marlin-userdebug; mostly because of the
protobuf serialization).

Test: atest WmTests:ActivityMetricsLaunchObserverTests
Bug: 72170747
Bug: 112680320
Change-Id: Icc07b727b50438bdd65bb01978e15d839809948b
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
index e3133ef..eff0f75 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
@@ -42,7 +42,7 @@
  * It must then transition to either {@code CANCELLED} with {@link #onActivityLaunchCancelled}
  * or into {@code FINISHED} with {@link #onActivityLaunchFinished}. These are terminal states.
  *
- * Note that the {@link ActivityRecord} provided as a parameter to some state transitions isn't
+ * Note that the {@code ActivityRecordProto} provided as a parameter to some state transitions isn't
  * necessarily the same within a single launch sequence: it is only the top-most activity at the
  * time (if any). Trampoline activities coalesce several activity starts into a single launch
  * sequence.
@@ -94,6 +94,14 @@
     public static final int TEMPERATURE_HOT = 3;
 
     /**
+     * Typedef marker that a {@code byte[]} actually contains an
+     * <a href="proto/android/server/activitymanagerservice.proto">ActivityRecordProto</a>
+     * in the protobuf format.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ActivityRecordProto {}
+
+    /**
      * Notifies the observer that a new launch sequence has begun as a result of a new intent.
      *
      * Once a launch sequence begins, the resolved activity will either subsequently start with
@@ -135,7 +143,7 @@
      * Multiple calls to this method cannot occur without first terminating the current
      * launch sequence.
      */
-    public void onActivityLaunched(@NonNull ActivityRecord activity,
+    public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity,
                                    @Temperature int temperature);
 
     /**
@@ -157,7 +165,7 @@
      *          in the case of a trampoline, multiple activities could've been started
      *          and only the latest activity is reported here.
      */
-    public void onActivityLaunchCancelled(@Nullable ActivityRecord abortingActivity);
+    public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] abortingActivity);
 
     /**
      * Notifies the observer that the current launch sequence has been successfully finished.
@@ -178,5 +186,5 @@
      *          and only the latest activity that was top-most during first-frame drawn
      *          is reported here.
      */
-    public void onActivityLaunchFinished(@NonNull ActivityRecord finalActivity);
+    public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] finalActivity);
 }
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserverRegistry.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserverRegistry.java
new file mode 100644
index 0000000..fa90dc5
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserverRegistry.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2018 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.wm;
+
+import android.annotation.NonNull;
+
+/**
+ * Multi-cast delegate implementation for {@link ActivityMetricsLaunchObserver}.
+ *
+ * <br/><br/>
+ * This enables multiple launch observers to subscribe to {@link ActivityMetricsLogger}
+ * independently of each other.
+ *
+ * <br/><br/>
+ * Some callbacks in {@link ActivityMetricsLaunchObserver} have a {@code byte[]}
+ * parameter; this array is reused by all the registered observers, so it must not be written to
+ * (i.e. all observers must treat any array parameters as immutable).
+ *
+ * <br /><br />
+ * Multi-cast invocations occurs sequentially in-order of registered observers.
+ */
+public interface ActivityMetricsLaunchObserverRegistry {
+    /**
+     * Register an extra launch observer to receive the multi-cast.
+     *
+     * <br /><br />
+     * Multi-cast invocation happens in the same order the observers were registered. For example,
+     * <pre>
+     *     registerLaunchObserver(A)
+     *     registerLaunchObserver(B)
+     *
+     *     obs.onIntentFailed() ->
+     *       A.onIntentFailed()
+     *       B.onIntentFailed()
+     * </pre>
+     */
+    void registerLaunchObserver(@NonNull ActivityMetricsLaunchObserver launchObserver);
+
+    /**
+     * Unregister an existing launch observer. It will not receive the multi-cast in the future.
+     *
+     * <br /><br />
+     * This does nothing if this observer was not already registered.
+     */
+    void unregisterLaunchObserver(@NonNull ActivityMetricsLaunchObserver launchObserver);
+}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 1c08d03..a7bf96b 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -99,10 +99,12 @@
 import android.util.SparseIntArray;
 import android.util.StatsLog;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 
 /**
@@ -168,7 +170,8 @@
      * Due to the global single concurrent launch sequence, all calls to this observer must be made
      * in-order on the same thread to fulfill the "happens-before" guarantee in LaunchObserver.
      */
-    private final ActivityMetricsLaunchObserver mLaunchObserver = null;
+    private final LaunchObserverRegistryImpl mLaunchObserver;
+    @VisibleForTesting static final int LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE = 512;
 
     private final class H extends Handler {
 
@@ -263,6 +266,7 @@
         mSupervisor = supervisor;
         mContext = context;
         mHandler = new H(looper);
+        mLaunchObserver = new LaunchObserverRegistryImpl(looper);
     }
 
     void logWindowState() {
@@ -993,12 +997,19 @@
         }
     }
 
+    public ActivityMetricsLaunchObserverRegistry getLaunchObserverRegistry() {
+        return mLaunchObserver;
+    }
+
     /** Notify the {@link ActivityMetricsLaunchObserver} that a new launch sequence has begun. */
     private void launchObserverNotifyIntentStarted(Intent intent) {
-        if (mLaunchObserver != null) {
-            // Beginning a launch is timing sensitive and so should be observed as soon as possible.
-            mLaunchObserver.onIntentStarted(intent);
-        }
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                "MetricsLogger:launchObserverNotifyIntentStarted");
+
+        // Beginning a launch is timing sensitive and so should be observed as soon as possible.
+        mLaunchObserver.onIntentStarted(intent);
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     /**
@@ -1007,9 +1018,12 @@
      * intent being delivered to the top running activity.
      */
     private void launchObserverNotifyIntentFailed() {
-        if (mLaunchObserver != null) {
-            mLaunchObserver.onIntentFailed();
-        }
+       Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                "MetricsLogger:launchObserverNotifyIntentFailed");
+
+        mLaunchObserver.onIntentFailed();
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     /**
@@ -1017,14 +1031,17 @@
      * has started.
      */
     private void launchObserverNotifyActivityLaunched(WindowingModeTransitionInfo info) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                "MetricsLogger:launchObserverNotifyActivityLaunched");
+
         @ActivityMetricsLaunchObserver.Temperature int temperature =
                 convertTransitionTypeToLaunchObserverTemperature(getTransitionType(info));
 
-        if (mLaunchObserver != null) {
-            // Beginning a launch is timing sensitive and so should be observed as soon as possible.
-            mLaunchObserver.onActivityLaunched(info.launchedActivity,
-                                               temperature);
-        }
+        // Beginning a launch is timing sensitive and so should be observed as soon as possible.
+        mLaunchObserver.onActivityLaunched(convertActivityRecordToProto(info.launchedActivity),
+                                           temperature);
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     /**
@@ -1032,11 +1049,15 @@
      * cancelled.
      */
     private void launchObserverNotifyActivityLaunchCancelled(WindowingModeTransitionInfo info) {
-        final ActivityRecord launchedActivity = info != null ? info.launchedActivity : null;
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                "MetricsLogger:launchObserverNotifyActivityLaunchCancelled");
 
-        if (mLaunchObserver != null) {
-            mLaunchObserver.onActivityLaunchCancelled(launchedActivity);
-        }
+        final @ActivityMetricsLaunchObserver.ActivityRecordProto byte[] activityRecordProto =
+                info != null ? convertActivityRecordToProto(info.launchedActivity) : null;
+
+        mLaunchObserver.onActivityLaunchCancelled(activityRecordProto);
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     /**
@@ -1044,11 +1065,34 @@
      * has fully finished (successfully).
      */
     private void launchObserverNotifyActivityLaunchFinished(WindowingModeTransitionInfo info) {
-        final ActivityRecord launchedActivity = info.launchedActivity;
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                "MetricsLogger:launchObserverNotifyActivityLaunchFinished");
 
-        if (mLaunchObserver != null) {
-            mLaunchObserver.onActivityLaunchFinished(launchedActivity);
-        }
+        mLaunchObserver.onActivityLaunchFinished(
+                convertActivityRecordToProto(info.launchedActivity));
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    @VisibleForTesting
+    static @ActivityMetricsLaunchObserver.ActivityRecordProto byte[]
+            convertActivityRecordToProto(ActivityRecord record) {
+        // May take non-negligible amount of time to convert ActivityRecord into a proto,
+        // so track the time.
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                "MetricsLogger:convertActivityRecordToProto");
+
+        // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream,
+        // so create a new one every time.
+        final ProtoOutputStream protoOutputStream =
+                new ProtoOutputStream(LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
+        // Write this data out as the top-most ActivityRecordProto (i.e. it is not a sub-object).
+        record.writeToProto(protoOutputStream);
+        final byte[] bytes = protoOutputStream.getBytes();
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        return bytes;
     }
 
     private static @ActivityMetricsLaunchObserver.Temperature int
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5e92b9e..cdb3018 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2946,8 +2946,11 @@
         proto.end(token);
     }
 
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
+    /**
+     * Write all fields to an {@code ActivityRecordProto}. This assumes the
+     * {@code ActivityRecordProto} is the outer-most proto data.
+     */
+    void writeToProto(ProtoOutputStream proto) {
         super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
         writeIdentifierToProto(proto, IDENTIFIER);
         proto.write(STATE, mState.toString());
@@ -2957,6 +2960,11 @@
             proto.write(PROC_ID, app.getPid());
         }
         proto.write(TRANSLUCENT, !fullscreen);
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        writeToProto(proto);
         proto.end(token);
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 694e9d1..ad2413b 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -634,7 +634,9 @@
 
         mInitialized = true;
         mRunningTasks = createRunningTasks();
-        mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext, mHandler.getLooper());
+
+        mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext,
+                mHandler.getLooper());
         mKeyguardController = new KeyguardController(mService, this);
 
         mPersisterQueue = new PersisterQueue();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index d665592..0cdbedb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -473,4 +473,6 @@
     public abstract void setProfileApp(String profileApp);
     public abstract void setProfileProc(WindowProcessController wpc);
     public abstract void setProfilerInfo(ProfilerInfo profilerInfo);
+
+    public abstract ActivityMetricsLaunchObserverRegistry getLaunchObserverRegistry();
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 4f01d699..d8bc3bc 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6870,5 +6870,12 @@
                 mProfilerInfo = profilerInfo;
             }
         }
+
+        @Override
+        public ActivityMetricsLaunchObserverRegistry getLaunchObserverRegistry() {
+            synchronized (mGlobalLock) {
+                return mStackSupervisor.getActivityMetricsLogger().getLaunchObserverRegistry();
+            }
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
new file mode 100644
index 0000000..93e2d8d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2018 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.wm;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.util.ArrayList;
+
+/**
+ * Multi-cast implementation of {@link ActivityMetricsLaunchObserver}.
+ *
+ * <br /><br />
+ * If this class is called through the {@link ActivityMetricsLaunchObserver} interface,
+ * then the call is forwarded to all registered observers at the time.
+ *
+ * <br /><br />
+ * All calls are invoked asynchronously in-order on a background thread. This fulfills the
+ * sequential ordering guarantee in {@link ActivityMetricsLaunchObserverRegistry}.
+ *
+ * @see ActivityTaskManagerInternal#getLaunchObserverRegistry()
+ */
+class LaunchObserverRegistryImpl implements
+        ActivityMetricsLaunchObserverRegistry, ActivityMetricsLaunchObserver {
+    private final ArrayList<ActivityMetricsLaunchObserver> mList = new ArrayList<>();
+
+    /**
+     * All calls are posted to a handler because:
+     *
+     * 1. We don't know how long the observer will take to handle this call and we don't want
+     *    to block the WM critical section on it.
+     * 2. We don't know the lock ordering of the observer so we don't want to expose a chance
+     *    of deadlock.
+     */
+    private final Handler mHandler;
+
+    public LaunchObserverRegistryImpl(Looper looper) {
+        mHandler = new Handler(looper);
+    }
+
+    @Override
+    public void registerLaunchObserver(ActivityMetricsLaunchObserver launchObserver) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleRegisterLaunchObserver, this, launchObserver));
+    }
+
+    @Override
+    public void unregisterLaunchObserver(ActivityMetricsLaunchObserver launchObserver) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleUnregisterLaunchObserver, this, launchObserver));
+    }
+
+    @Override
+    public void onIntentStarted(Intent intent) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleOnIntentStarted, this, intent));
+    }
+
+    @Override
+    public void onIntentFailed() {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleOnIntentFailed, this));
+    }
+
+    @Override
+    public void onActivityLaunched(
+            @ActivityRecordProto byte[] activity,
+            int temperature) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleOnActivityLaunched,
+                this, activity, temperature));
+    }
+
+    @Override
+    public void onActivityLaunchCancelled(
+        @ActivityRecordProto byte[] activity) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleOnActivityLaunchCancelled, this, activity));
+    }
+
+    @Override
+    public void onActivityLaunchFinished(
+        @ActivityRecordProto byte[] activity) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleOnActivityLaunchFinished, this, activity));
+    }
+
+    // Use PooledLambda.obtainMessage to invoke below methods. Every method reference must be
+    // unbound (i.e. not capture any variables explicitly or implicitly) to fulfill the
+    // singleton-lambda requirement.
+
+    private void handleRegisterLaunchObserver(ActivityMetricsLaunchObserver observer) {
+        mList.add(observer);
+    }
+
+    private void handleUnregisterLaunchObserver(ActivityMetricsLaunchObserver observer) {
+        mList.remove(observer);
+    }
+
+    private void handleOnIntentStarted(Intent intent) {
+        // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+        for (int i = 0; i < mList.size(); i++) {
+             ActivityMetricsLaunchObserver o = mList.get(i);
+             o.onIntentStarted(intent);
+        }
+    }
+
+    private void handleOnIntentFailed() {
+        // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+        for (int i = 0; i < mList.size(); i++) {
+             ActivityMetricsLaunchObserver o = mList.get(i);
+             o.onIntentFailed();
+        }
+    }
+
+    private void handleOnActivityLaunched(
+            @ActivityRecordProto byte[] activity,
+            @Temperature int temperature) {
+        // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+        for (int i = 0; i < mList.size(); i++) {
+             ActivityMetricsLaunchObserver o = mList.get(i);
+             o.onActivityLaunched(activity, temperature);
+        }
+    }
+
+    private void handleOnActivityLaunchCancelled(
+            @ActivityRecordProto byte[] activity) {
+        // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+        for (int i = 0; i < mList.size(); i++) {
+             ActivityMetricsLaunchObserver o = mList.get(i);
+             o.onActivityLaunchCancelled(activity);
+        }
+    }
+
+    private void handleOnActivityLaunchFinished(
+            @ActivityRecordProto byte[] activity) {
+        // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+        for (int i = 0; i < mList.size(); i++) {
+            ActivityMetricsLaunchObserver o = mList.get(i);
+            o.onActivityLaunchFinished(activity);
+        }
+    }
+}