diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index aae6f50168917..f8eb95cbd48cc 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -59,6 +59,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; import android.annotation.IntDef; @@ -171,6 +172,7 @@ public class InteractionJankMonitor { public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34; public static final int CUJ_PIP_TRANSITION = 35; public static final int CUJ_WALLPAPER_TRANSITION = 36; + public static final int CUJ_USER_SWITCH = 37; private static final int NO_STATSD_LOGGING = -1; @@ -216,6 +218,7 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH, }; private static volatile InteractionJankMonitor sInstance; @@ -272,6 +275,7 @@ public class InteractionJankMonitor { CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, CUJ_PIP_TRANSITION, CUJ_WALLPAPER_TRANSITION, + CUJ_USER_SWITCH, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -642,6 +646,8 @@ public class InteractionJankMonitor { return "PIP_TRANSITION"; case CUJ_WALLPAPER_TRANSITION: return "WALLPAPER_TRANSITION"; + case CUJ_USER_SWITCH: + return "USER_SWITCH"; } return "UNKNOWN"; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 92d1bc4173d86..5969e9290c9c9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -90,6 +90,7 @@ import androidx.annotation.Nullable; import androidx.lifecycle.Observer; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.WirelessUtils; import com.android.settingslib.fuelgauge.BatteryStatus; @@ -321,6 +322,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mIsDreaming; private final DevicePolicyManager mDevicePolicyManager; private final BroadcastDispatcher mBroadcastDispatcher; + private final InteractionJankMonitor mInteractionJankMonitor; private boolean mLogoutEnabled; // cached value to avoid IPCs private boolean mIsUdfpsEnrolled; @@ -1770,6 +1772,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab AuthController authController, TelephonyListenerManager telephonyListenerManager, FeatureFlags featureFlags, + InteractionJankMonitor interactionJankMonitor, @Nullable Vibrator vibrator) { mContext = context; mSubscriptionManager = SubscriptionManager.from(context); @@ -1778,6 +1781,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged); mBackgroundExecutor = backgroundExecutor; mBroadcastDispatcher = broadcastDispatcher; + mInteractionJankMonitor = interactionJankMonitor; mRingerModeTracker = ringerModeTracker; mStatusBarStateController = statusBarStateController; mStatusBarStateController.addCallback(mStatusBarStateControllerListener); @@ -2637,7 +2641,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * Handle {@link #MSG_USER_SWITCH_COMPLETE} */ - private void handleUserSwitchComplete(int userId) { + @VisibleForTesting + void handleUserSwitchComplete(int userId) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -2645,6 +2650,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onUserSwitchComplete(userId); } } + mInteractionJankMonitor.end(InteractionJankMonitor.CUJ_USER_SWITCH); } /** diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 4d1608fb445d0..d74df37401d00 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -74,6 +74,7 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.app.IBatteryStats; import com.android.internal.appwidget.IAppWidgetService; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.LatencyTracker; import com.android.systemui.dagger.qualifiers.DisplayId; @@ -218,6 +219,12 @@ public class FrameworkServicesModule { return context.getSystemService(FingerprintManager.class); } + @Provides + @Singleton + static InteractionJankMonitor provideInteractionJankMonitor() { + return InteractionJankMonitor.getInstance(); + } + @Provides @Singleton static InputMethodManager provideInputMethodManager(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 7dd1d719723dd..3bdbc054dd458 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1145,6 +1145,8 @@ public class StatusBar extends SystemUI implements mNotificationPanelViewController.addExpansionListener( this::dispatchPanelExpansionForKeyguardDismiss); + mUserSwitcherController.init(mNotificationShadeWindowView); + // Allow plugins to reference DarkIconDispatcher and StatusBarStateController mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class); mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 22f08ada5fc4b..dadc01664b4d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -25,6 +25,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; +import android.app.IActivityManager; import android.app.IActivityTaskManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -55,6 +56,7 @@ import android.view.WindowManagerGlobal; import android.widget.BaseAdapter; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.RestrictedLockUtilsInternal; @@ -75,6 +77,7 @@ import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.qs.tiles.UserDetailView; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.CreateUserActivity; @@ -107,6 +110,7 @@ public class UserSwitcherController implements Dumpable { private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000; private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; + private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000l; protected final Context mContext; protected final UserTracker mUserTracker; @@ -123,6 +127,7 @@ public class UserSwitcherController implements Dumpable { private final BroadcastDispatcher mBroadcastDispatcher; private final TelephonyListenerManager mTelephonyListenerManager; private final IActivityTaskManager mActivityTaskManager; + private final InteractionJankMonitor mInteractionJankMonitor; private ArrayList mUsers = new ArrayList<>(); @VisibleForTesting @@ -141,15 +146,18 @@ public class UserSwitcherController implements Dumpable { private Intent mSecondaryUserServiceIntent; private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2); private final UiEventLogger mUiEventLogger; + private final IActivityManager mActivityManager; public final DetailAdapter mUserDetailAdapter; private final Executor mBgExecutor; private final boolean mGuestUserAutoCreated; private final AtomicBoolean mGuestIsResetting; private final AtomicBoolean mGuestCreationScheduled; private FalsingManager mFalsingManager; + private NotificationShadeWindowView mRootView; @Inject public UserSwitcherController(Context context, + IActivityManager activityManager, UserManager userManager, UserTracker userTracker, KeyguardStateController keyguardStateController, @@ -165,14 +173,17 @@ public class UserSwitcherController implements Dumpable { UserDetailAdapter userDetailAdapter, SecureSettings secureSettings, @Background Executor bgExecutor, + InteractionJankMonitor interactionJankMonitor, DumpManager dumpManager) { mContext = context; + mActivityManager = activityManager; mUserTracker = userTracker; mBroadcastDispatcher = broadcastDispatcher; mTelephonyListenerManager = telephonyListenerManager; mActivityTaskManager = activityTaskManager; mUiEventLogger = uiEventLogger; mFalsingManager = falsingManager; + mInteractionJankMonitor = interactionJankMonitor; mGuestResumeSessionReceiver = new GuestResumeSessionReceiver( this, mUserTracker, mUiEventLogger, secureSettings); mUserDetailAdapter = userDetailAdapter; @@ -485,8 +496,11 @@ public class UserSwitcherController implements Dumpable { protected void switchToUserId(int id) { try { + mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder + .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mRootView) + .setTimeout(MULTI_USER_JOURNEY_TIMEOUT)); pauseRefreshUsers(); - ActivityManager.getService().switchUser(id); + mActivityManager.switchUser(id); } catch (RemoteException e) { Log.e(TAG, "Couldn't switch user.", e); } @@ -793,6 +807,10 @@ public class UserSwitcherController implements Dumpable { return guest.id; } + public void init(NotificationShadeWindowView notificationShadeWindowView) { + mRootView = notificationShadeWindowView; + } + public static abstract class BaseUserAdapter extends BaseAdapter { final UserSwitcherController mController; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index d3557d4b1809e..0772b20985655 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -78,6 +78,7 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; @@ -171,6 +172,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private FeatureFlags mFeatureFlags; @Mock + private InteractionJankMonitor mInteractionJankMonitor; + @Mock private Vibrator mVibrator; @Captor private ArgumentCaptor mStatusBarStateListenerCaptor; @@ -736,6 +739,16 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(0); } + @Test + public void testMultiUserJankMonitor_whenUserSwitches() throws Exception { + final IRemoteCallback reply = new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) {} // do nothing + }; + mKeyguardUpdateMonitor.handleUserSwitchComplete(10 /* user */); + verify(mInteractionJankMonitor).end(eq(InteractionJankMonitor.CUJ_USER_SWITCH)); + } + @Test public void testGetUserCanSkipBouncer_whenTrust() { int user = KeyguardUpdateMonitor.getCurrentUser(); @@ -1051,7 +1064,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mRingerModeTracker, mBackgroundExecutor, mStatusBarStateController, mLockPatternUtils, mAuthController, mTelephonyListenerManager, mFeatureFlags, - mVibrator); + mInteractionJankMonitor, mVibrator); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt index dba83e1eeeb99..dd43ea56609bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy +import android.app.IActivityManager import android.app.IActivityTaskManager import android.app.admin.DevicePolicyManager import android.content.Context @@ -31,6 +32,7 @@ import android.os.UserManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.util.UserIcons import com.android.systemui.GuestResumeSessionReceiver @@ -54,10 +56,11 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock +import org.mockito.Mockito.`when` import org.mockito.Mockito.any import org.mockito.Mockito.anyString import org.mockito.Mockito.mock -import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -65,6 +68,7 @@ import org.mockito.MockitoAnnotations @SmallTest class UserSwitcherControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var activityManager: IActivityManager @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Mock private lateinit var handler: Handler @@ -78,6 +82,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Mock private lateinit var secureSettings: SecureSettings @Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor private lateinit var testableLooper: TestableLooper private lateinit var uiBgExecutor: FakeExecutor private lateinit var uiEventLogger: UiEventLoggerFake @@ -110,6 +115,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { userSwitcherController = UserSwitcherController( context, + activityManager, userManager, userTracker, keyguardStateController, @@ -125,6 +131,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { userDetailAdapter, secureSettings, uiBgExecutor, + interactionJankMonitor, dumpManager) userSwitcherController.mPauseRefreshUsers = true @@ -132,7 +139,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { } @Test - fun testAddGuest_okButtonPressed_isLogged() { + fun testAddGuest_okButtonPressed() { val emptyGuestUserRecord = UserSwitcherController.UserRecord( null, null, @@ -148,6 +155,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { userSwitcherController.onUserListItemClicked(emptyGuestUserRecord) testableLooper.processAllMessages() + verify(interactionJankMonitor).begin(any()) + verify(activityManager).switchUser(guestInfo.id) assertEquals(1, uiEventLogger.numLogs()) assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0)) }