diff options
9 files changed, 392 insertions, 46 deletions
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java index e3133efb890c..eff0f75466d9 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 @@ import java.lang.annotation.RetentionPolicy; * 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 interface ActivityMetricsLaunchObserver { 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 @@ public interface ActivityMetricsLaunchObserver { * 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 @@ public interface ActivityMetricsLaunchObserver { * 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 @@ public interface ActivityMetricsLaunchObserver { * 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 000000000000..fa90dc5b83f4 --- /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 416e133ea14a..16df52d4ef65 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.SparseArray; 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 @@ class ActivityMetricsLogger { * 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 @@ class ActivityMetricsLogger { mSupervisor = supervisor; mContext = context; mHandler = new H(looper); + mLaunchObserver = new LaunchObserverRegistryImpl(looper); } void logWindowState() { @@ -1000,12 +1004,19 @@ class ActivityMetricsLogger { } } + 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); } /** @@ -1014,9 +1025,12 @@ class ActivityMetricsLogger { * 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); } /** @@ -1024,14 +1038,17 @@ class ActivityMetricsLogger { * 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); } /** @@ -1039,11 +1056,15 @@ class ActivityMetricsLogger { * 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); } /** @@ -1051,11 +1072,34 @@ class ActivityMetricsLogger { * 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 b4aec35d9d6e..6f2461bd8489 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3215,8 +3215,11 @@ final class ActivityRecord extends ConfigurationContainer { 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()); @@ -3226,6 +3229,11 @@ final class ActivityRecord extends ConfigurationContainer { 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 3162ee37276e..987c706b0c4e 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -434,7 +434,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { 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 d6655928105e..0cdbedba7318 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 class ActivityTaskManagerInternal { 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 11e998c7ec89..e1a1e6125104 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6894,5 +6894,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mProfilerInfo = profilerInfo; } } + + @Override + public ActivityMetricsLaunchObserverRegistry getLaunchObserverRegistry() { + synchronized (mGlobalLock) { + return mStackSupervisor.getActivityMetricsLogger().getLaunchObserverRegistry(); + } + } } } 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 000000000000..93e2d8d6fba4 --- /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); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 0e30037bde6c..cac9cf69ce4d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -24,20 +24,30 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.timeout; import android.content.Intent; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.util.SparseIntArray; +import android.util.proto.ProtoOutputStream; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; +import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; + +import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentMatcher; + +import java.util.Arrays; /** * Tests for the {@link ActivityMetricsLaunchObserver} class. @@ -51,6 +61,7 @@ import org.junit.Test; public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { private ActivityMetricsLogger mActivityMetricsLogger; private ActivityMetricsLaunchObserver mLaunchObserver; + private ActivityMetricsLaunchObserverRegistry mLaunchObserverRegistry; private TestActivityStack mStack; private TaskRecord mTask; @@ -61,16 +72,13 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { public void setUpAMLO() throws Exception { setupActivityTaskManagerService(); - mActivityMetricsLogger = - new ActivityMetricsLogger(mSupervisor, mService.mContext, mService.mH.getLooper()); - mLaunchObserver = mock(ActivityMetricsLaunchObserver.class); - // TODO: Use ActivityMetricsLaunchObserverRegistry . - java.lang.reflect.Field f = - mActivityMetricsLogger.getClass().getDeclaredField("mLaunchObserver"); - f.setAccessible(true); - f.set(mActivityMetricsLogger, mLaunchObserver); + // ActivityStackSupervisor always creates its own instance of ActivityMetricsLogger. + mActivityMetricsLogger = mSupervisor.getActivityMetricsLogger(); + + mLaunchObserverRegistry = mActivityMetricsLogger.getLaunchObserverRegistry(); + mLaunchObserverRegistry.registerLaunchObserver(mLaunchObserver); // Sometimes we need an ActivityRecord for ActivityMetricsLogger to do anything useful. // This seems to be the easiest way to create an ActivityRecord. @@ -81,13 +89,45 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { mActivityRecordTrampoline = new ActivityBuilder(mService).setTask(mTask).build(); } + @After + public void tearDownAMLO() throws Exception { + if (mLaunchObserverRegistry != null) { // Don't NPE if setUp failed. + mLaunchObserverRegistry.unregisterLaunchObserver(mLaunchObserver); + } + } + + static class ActivityRecordMatcher implements ArgumentMatcher</*@ActivityRecordProto*/ byte[]> { + private final @ActivityRecordProto byte[] mExpected; + + public ActivityRecordMatcher(ActivityRecord activityRecord) { + mExpected = activityRecordToProto(activityRecord); + } + + public boolean matches(@ActivityRecordProto byte[] actual) { + return Arrays.equals(mExpected, actual); + } + } + + static @ActivityRecordProto byte[] activityRecordToProto(ActivityRecord record) { + return ActivityMetricsLogger.convertActivityRecordToProto(record); + } + + static @ActivityRecordProto byte[] eqProto(ActivityRecord record) { + return argThat(new ActivityRecordMatcher(record)); + } + + static <T> T verifyAsync(T mock) { + // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout. + return verify(mock, timeout(100)); + } + @Test public void testOnIntentStarted() throws Exception { Intent intent = new Intent("action 1"); mActivityMetricsLogger.notifyActivityLaunching(intent); - verify(mLaunchObserver).onIntentStarted(eq(intent)); + verifyAsync(mLaunchObserver).onIntentStarted(eq(intent)); verifyNoMoreInteractions(mLaunchObserver); } @@ -102,7 +142,7 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, activityRecord); - verify(mLaunchObserver).onIntentFailed(); + verifyAsync(mLaunchObserver).onIntentFailed(); verifyNoMoreInteractions(mLaunchObserver); } @@ -113,7 +153,7 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mActivityRecord); - verify(mLaunchObserver).onActivityLaunched(eq(mActivityRecord), anyInt()); + verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mActivityRecord), anyInt()); verifyNoMoreInteractions(mLaunchObserver); } @@ -127,7 +167,7 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecord.getWindowingMode(), SystemClock.uptimeMillis()); - verify(mLaunchObserver).onActivityLaunchFinished(eq(mActivityRecord)); + verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecord)); verifyNoMoreInteractions(mLaunchObserver); } @@ -135,12 +175,12 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { public void testOnActivityLaunchCancelled() throws Exception { testOnActivityLaunched(); - mActivityRecord.nowVisible = true; + mActivityRecord.mDrawn = true; // Cannot time already-visible activities. mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mActivityRecord); - verify(mLaunchObserver).onActivityLaunchCancelled(eq(mActivityRecord)); + verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mActivityRecord)); verifyNoMoreInteractions(mLaunchObserver); } @@ -151,7 +191,7 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mActivityRecord); - verify(mLaunchObserver).onActivityLaunched(eq(mActivityRecord), anyInt()); + verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mActivityRecord), anyInt()); // A second, distinct, activity launch is coalesced into the the current app launch sequence mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, @@ -170,7 +210,7 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecordTrampoline.getWindowingMode(), SystemClock.uptimeMillis()); - verify(mLaunchObserver).onActivityLaunchFinished(eq(mActivityRecordTrampoline)); + verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecordTrampoline)); verifyNoMoreInteractions(mLaunchObserver); } @@ -178,13 +218,26 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { public void testOnActivityLaunchCancelledTrampoline() throws Exception { testOnActivityLaunchedTrampoline(); - mActivityRecordTrampoline.nowVisible = true; + mActivityRecordTrampoline.mDrawn = true; // Cannot time already-visible activities. mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mActivityRecordTrampoline); - verify(mLaunchObserver).onActivityLaunchCancelled(eq(mActivityRecordTrampoline)); + verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mActivityRecordTrampoline)); verifyNoMoreInteractions(mLaunchObserver); } + + @Test + public void testActivityRecordProtoIsNotTooBig() throws Exception { + // The ActivityRecordProto must not be too big, otherwise converting it at runtime + // will become prohibitively expensive. + assertWithMessage("mActivityRecord: %s", mActivityRecord). + that(activityRecordToProto(mActivityRecord).length). + isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE); + + assertWithMessage("mActivityRecordTrampoline: %s", mActivityRecordTrampoline). + that(activityRecordToProto(mActivityRecordTrampoline).length). + isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE); + } } |