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);
+ }
+ }
+}