Add interface for services to share SessionIds
- adds ability to start/end sessions via StatusBarService's SessionMonitor - adds ability to register/unregister for session listeners via StatusBarService's SessionMonitor - adds logic for SystemUI to start/end Keyguard and BiometricPrompt sessions in SessionTracker Test: atest AuthControllerTest SessionTrackerTest Bug: 213483562 Change-Id: I50ea003baae4fb89e311fa442fd52397e03e514c
This commit is contained in:
@@ -44,6 +44,7 @@ import com.android.internal.statusbar.NotificationVisibility;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -213,6 +214,34 @@ public class StatusBarManager {
|
||||
/** @hide */
|
||||
public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2;
|
||||
|
||||
/**
|
||||
* Session flag for {@link #registerSessionListener} indicating the listener
|
||||
* is interested in sessions on the keygaurd
|
||||
* @hide
|
||||
*/
|
||||
public static final int SESSION_KEYGUARD = 1 << 0;
|
||||
|
||||
/**
|
||||
* Session flag for {@link #registerSessionListener} indicating the current session
|
||||
* is interested in session on the biometric prompt.
|
||||
* @hide
|
||||
*/
|
||||
public static final int SESSION_BIOMETRIC_PROMPT = 1 << 1;
|
||||
|
||||
/** @hide */
|
||||
public static final Set<Integer> ALL_SESSIONS = Set.of(
|
||||
SESSION_KEYGUARD,
|
||||
SESSION_BIOMETRIC_PROMPT
|
||||
);
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(flag = true, prefix = { "SESSION_KEYGUARD" }, value = {
|
||||
SESSION_KEYGUARD,
|
||||
SESSION_BIOMETRIC_PROMPT,
|
||||
})
|
||||
public @interface SessionFlags {}
|
||||
|
||||
/**
|
||||
* Response indicating that the tile was not added.
|
||||
*/
|
||||
|
||||
19
core/java/com/android/internal/logging/InstanceId.aidl
Normal file
19
core/java/com/android/internal/logging/InstanceId.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.internal.logging;
|
||||
|
||||
parcelable InstanceId;
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2022, 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 permissons and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.internal.statusbar;
|
||||
|
||||
import com.android.internal.logging.InstanceId;
|
||||
|
||||
/** {@hide} */
|
||||
oneway interface ISessionListener {
|
||||
void onSessionStarted(int sessionType, in InstanceId instance);
|
||||
void onSessionEnded(int sessionType, in InstanceId instance);
|
||||
}
|
||||
@@ -28,7 +28,9 @@ import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
|
||||
import com.android.internal.logging.InstanceId;
|
||||
import com.android.internal.statusbar.IAddTileResultCallback;
|
||||
import com.android.internal.statusbar.ISessionListener;
|
||||
import com.android.internal.statusbar.IStatusBar;
|
||||
import com.android.internal.statusbar.RegisterStatusBarResult;
|
||||
import com.android.internal.statusbar.StatusBarIcon;
|
||||
@@ -178,4 +180,17 @@ interface IStatusBarService
|
||||
* @hide
|
||||
*/
|
||||
int getNavBarModeOverride();
|
||||
|
||||
/**
|
||||
* Register a listener for certain sessions. Each session may be guarded by its own permission.
|
||||
*/
|
||||
void registerSessionListener(int sessionFlags, in ISessionListener listener);
|
||||
void unregisterSessionListener(int sessionFlags, in ISessionListener listener);
|
||||
|
||||
/**
|
||||
* Informs all registered listeners that a session has begun and has the following instanceId.
|
||||
* Can only be set by callers with certain permission based on the session type being updated.
|
||||
*/
|
||||
void onSessionStarted(int sessionType, in InstanceId instanceId);
|
||||
void onSessionEnded(int sessionType, in InstanceId instanceId);
|
||||
}
|
||||
|
||||
@@ -309,6 +309,7 @@
|
||||
<item>com.android.systemui.globalactions.GlobalActionsComponent</item>
|
||||
<item>com.android.systemui.ScreenDecorations</item>
|
||||
<item>com.android.systemui.biometrics.AuthController</item>
|
||||
<item>com.android.systemui.log.SessionTracker</item>
|
||||
<item>com.android.systemui.SliceBroadcastRelayHandler</item>
|
||||
<item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
|
||||
<item>com.android.systemui.theme.ThemeOverlayController</item>
|
||||
|
||||
@@ -168,6 +168,10 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
|
||||
mCurrentDialog = null;
|
||||
mOrientationListener.disable();
|
||||
|
||||
for (Callback cb : mCallbacks) {
|
||||
cb.onBiometricPromptDismissed();
|
||||
}
|
||||
|
||||
try {
|
||||
if (mReceiver != null) {
|
||||
mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
|
||||
@@ -198,6 +202,10 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
|
||||
mCurrentDialog = null;
|
||||
mOrientationListener.disable();
|
||||
|
||||
for (Callback cb : mCallbacks) {
|
||||
cb.onBiometricPromptDismissed();
|
||||
}
|
||||
|
||||
if (mReceiver != null) {
|
||||
mReceiver.onDialogDismissed(
|
||||
BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
|
||||
@@ -460,6 +468,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
|
||||
Log.e(TAG, "sendResultAndCleanUp: Receiver is null");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mReceiver.onDialogDismissed(reason, credentialAttestation);
|
||||
} catch (RemoteException e) {
|
||||
@@ -811,6 +820,9 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
|
||||
}
|
||||
|
||||
mReceiver = (IBiometricSysuiReceiver) args.arg2;
|
||||
for (Callback cb : mCallbacks) {
|
||||
cb.onBiometricPromptShown();
|
||||
}
|
||||
mCurrentDialog = newDialog;
|
||||
mCurrentDialog.show(mWindowManager, savedState);
|
||||
mOrientationListener.enable();
|
||||
@@ -821,6 +833,11 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
|
||||
if (mCurrentDialog == null) {
|
||||
Log.w(TAG, "Dialog already dismissed");
|
||||
}
|
||||
|
||||
for (Callback cb : mCallbacks) {
|
||||
cb.onBiometricPromptDismissed();
|
||||
}
|
||||
|
||||
mReceiver = null;
|
||||
mCurrentDialog = null;
|
||||
mOrientationListener.disable();
|
||||
@@ -891,12 +908,22 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
|
||||
* Called when authenticators are registered. If authenticators are already
|
||||
* registered before this call, this callback will never be triggered.
|
||||
*/
|
||||
void onAllAuthenticatorsRegistered();
|
||||
default void onAllAuthenticatorsRegistered() {}
|
||||
|
||||
/**
|
||||
* Called when UDFPS enrollments have changed. This is called after boot and on changes to
|
||||
* enrollment.
|
||||
*/
|
||||
void onEnrollmentsChanged();
|
||||
default void onEnrollmentsChanged() {}
|
||||
|
||||
/**
|
||||
* Called when the biometric prompt starts showing.
|
||||
*/
|
||||
default void onBiometricPromptShown() {}
|
||||
|
||||
/**
|
||||
* Called when the biometric prompt is no longer showing.
|
||||
*/
|
||||
default void onBiometricPromptDismissed() {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.android.systemui.dreams.DreamOverlayRegistrant;
|
||||
import com.android.systemui.globalactions.GlobalActionsComponent;
|
||||
import com.android.systemui.keyguard.KeyguardViewMediator;
|
||||
import com.android.systemui.keyguard.dagger.KeyguardModule;
|
||||
import com.android.systemui.log.SessionTracker;
|
||||
import com.android.systemui.media.systemsounds.HomeSoundEffectController;
|
||||
import com.android.systemui.power.PowerUI;
|
||||
import com.android.systemui.privacy.television.TvOngoingPrivacyChip;
|
||||
@@ -66,6 +67,12 @@ public abstract class SystemUIBinder {
|
||||
@ClassKey(AuthController.class)
|
||||
public abstract CoreStartable bindAuthController(AuthController service);
|
||||
|
||||
/** Inject into SessionTracker. */
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SessionTracker.class)
|
||||
public abstract CoreStartable bindSessionTracker(SessionTracker service);
|
||||
|
||||
/** Inject into GarbageMonitor.Service. */
|
||||
@Binds
|
||||
@IntoMap
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.systemui.log;
|
||||
|
||||
import static android.app.StatusBarManager.ALL_SESSIONS;
|
||||
import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT;
|
||||
import static android.app.StatusBarManager.SESSION_KEYGUARD;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.logging.InstanceId;
|
||||
import com.android.internal.logging.InstanceIdSequence;
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||
import com.android.keyguard.KeyguardUpdateMonitorCallback;
|
||||
import com.android.systemui.CoreStartable;
|
||||
import com.android.systemui.biometrics.AuthController;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Track Session InstanceIds to be used for metrics logging to correlate logs in the same
|
||||
* session. Can be used across processes via StatusBarManagerService#registerSessionListener
|
||||
*/
|
||||
@SysUISingleton
|
||||
public class SessionTracker extends CoreStartable {
|
||||
private static final String TAG = "SessionTracker";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
// At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values
|
||||
private final InstanceIdSequence mInstanceIdGenerator = new InstanceIdSequence(1 << 20);
|
||||
|
||||
private final IStatusBarService mStatusBarManagerService;
|
||||
private final AuthController mAuthController;
|
||||
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
|
||||
private final KeyguardStateController mKeyguardStateController;
|
||||
private final Map<Integer, InstanceId> mSessionToInstanceId = new HashMap<>();
|
||||
|
||||
private boolean mKeyguardSessionStarted;
|
||||
|
||||
@Inject
|
||||
public SessionTracker(
|
||||
Context context,
|
||||
IStatusBarService statusBarService,
|
||||
AuthController authController,
|
||||
KeyguardUpdateMonitor keyguardUpdateMonitor,
|
||||
KeyguardStateController keyguardStateController
|
||||
) {
|
||||
super(context);
|
||||
mStatusBarManagerService = statusBarService;
|
||||
mAuthController = authController;
|
||||
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
||||
mKeyguardStateController = keyguardStateController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
mAuthController.addCallback(mAuthControllerCallback);
|
||||
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
|
||||
mKeyguardStateController.addCallback(mKeyguardStateCallback);
|
||||
|
||||
mKeyguardSessionStarted = mKeyguardStateController.isShowing();
|
||||
if (mKeyguardSessionStarted) {
|
||||
startSession(SESSION_KEYGUARD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the session ID associated with the passed session type.
|
||||
*/
|
||||
public @Nullable InstanceId getSessionId(int type) {
|
||||
return mSessionToInstanceId.getOrDefault(type, null);
|
||||
}
|
||||
|
||||
private void startSession(int type) {
|
||||
if (mSessionToInstanceId.getOrDefault(type, null) != null) {
|
||||
Log.e(TAG, "session [" + getString(type) + "] was already started");
|
||||
return;
|
||||
}
|
||||
|
||||
final InstanceId instanceId = mInstanceIdGenerator.newInstanceId();
|
||||
mSessionToInstanceId.put(type, instanceId);
|
||||
try {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Session start for [" + getString(type) + "] id=" + instanceId);
|
||||
}
|
||||
mStatusBarManagerService.onSessionStarted(type, instanceId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to send onSessionStarted for session="
|
||||
+ "[" + getString(type) + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void endSession(int type) {
|
||||
if (mSessionToInstanceId.getOrDefault(type, null) == null) {
|
||||
Log.e(TAG, "session [" + getString(type) + "] was not started");
|
||||
return;
|
||||
}
|
||||
|
||||
final InstanceId instanceId = mSessionToInstanceId.get(type);
|
||||
mSessionToInstanceId.put(type, null);
|
||||
try {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Session end for [" + getString(type) + "] id=" + instanceId);
|
||||
}
|
||||
mStatusBarManagerService.onSessionEnded(type, instanceId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to send onSessionEnded for session="
|
||||
+ "[" + getString(type) + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
public KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
|
||||
new KeyguardUpdateMonitorCallback() {
|
||||
@Override
|
||||
public void onStartedGoingToSleep(int why) {
|
||||
// we need to register to the KeyguardUpdateMonitor lifecycle b/c it gets called
|
||||
// before the WakefulnessLifecycle
|
||||
if (mKeyguardSessionStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
mKeyguardSessionStarted = true;
|
||||
startSession(SESSION_KEYGUARD);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public KeyguardStateController.Callback mKeyguardStateCallback =
|
||||
new KeyguardStateController.Callback() {
|
||||
public void onKeyguardShowingChanged() {
|
||||
boolean wasSessionStarted = mKeyguardSessionStarted;
|
||||
boolean keyguardShowing = mKeyguardStateController.isShowing();
|
||||
if (keyguardShowing && !wasSessionStarted) {
|
||||
mKeyguardSessionStarted = true;
|
||||
startSession(SESSION_KEYGUARD);
|
||||
} else if (!keyguardShowing && wasSessionStarted) {
|
||||
mKeyguardSessionStarted = false;
|
||||
endSession(SESSION_KEYGUARD);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
|
||||
@Override
|
||||
public void onBiometricPromptShown() {
|
||||
startSession(SESSION_BIOMETRIC_PROMPT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBiometricPromptDismissed() {
|
||||
endSession(SESSION_BIOMETRIC_PROMPT);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
|
||||
for (int session : ALL_SESSIONS) {
|
||||
pw.println(" " + getString(session)
|
||||
+ " instanceId=" + mSessionToInstanceId.get(session));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the string representation of a SINGLE SessionFlag. Combined SessionFlags will be
|
||||
* considered unknown.
|
||||
*/
|
||||
public static String getString(int sessionType) {
|
||||
if (sessionType == SESSION_KEYGUARD) {
|
||||
return "KEYGUARD";
|
||||
} else if (sessionType == SESSION_BIOMETRIC_PROMPT) {
|
||||
return "BIOMETRIC_PROMPT";
|
||||
}
|
||||
|
||||
return "unknownType=" + sessionType;
|
||||
}
|
||||
}
|
||||
@@ -625,6 +625,34 @@ public class AuthControllerTest extends SysuiTestCase {
|
||||
verify(mDisplayManager).unregisterDisplayListener(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnBiometricPromptShownCallback() {
|
||||
// GIVEN a callback is registered
|
||||
AuthController.Callback callback = mock(AuthController.Callback.class);
|
||||
mAuthController.addCallback(callback);
|
||||
|
||||
// WHEN dialog is shown
|
||||
showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
|
||||
|
||||
// THEN callback should be received
|
||||
verify(callback).onBiometricPromptShown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnBiometricPromptDismissedCallback() {
|
||||
// GIVEN a callback is registered
|
||||
AuthController.Callback callback = mock(AuthController.Callback.class);
|
||||
mAuthController.addCallback(callback);
|
||||
|
||||
// WHEN dialog is shown and then dismissed
|
||||
showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
|
||||
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
|
||||
null /* credentialAttestation */);
|
||||
|
||||
// THEN callback should be received
|
||||
verify(callback).onBiometricPromptDismissed();
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
private void showDialog(int[] sensorIds, boolean credentialAllowed) {
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.systemui.log;
|
||||
|
||||
import static android.app.StatusBarManager.ALL_SESSIONS;
|
||||
import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT;
|
||||
import static android.app.StatusBarManager.SESSION_KEYGUARD;
|
||||
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.logging.InstanceId;
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||
import com.android.keyguard.KeyguardUpdateMonitorCallback;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.biometrics.AuthController;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
@SmallTest
|
||||
public class SessionTrackerTest extends SysuiTestCase {
|
||||
@Mock
|
||||
private IStatusBarService mStatusBarService;
|
||||
@Mock
|
||||
private AuthController mAuthController;
|
||||
@Mock
|
||||
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
|
||||
@Mock
|
||||
private KeyguardStateController mKeyguardStateController;
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
|
||||
KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCallbackCaptor;
|
||||
KeyguardStateController.Callback mKeyguardStateCallback;
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
|
||||
AuthController.Callback mAuthControllerCallback;
|
||||
|
||||
private SessionTracker mSessionTracker;
|
||||
|
||||
@Before
|
||||
public void setup() throws RemoteException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mSessionTracker = new SessionTracker(
|
||||
mContext,
|
||||
mStatusBarService,
|
||||
mAuthController,
|
||||
mKeyguardUpdateMonitor,
|
||||
mKeyguardStateController
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartShowingKeyguard() throws RemoteException {
|
||||
// GIVEN the keyguard is showing before start
|
||||
when(mKeyguardStateController.isShowing()).thenReturn(true);
|
||||
|
||||
// WHEN started
|
||||
mSessionTracker.start();
|
||||
|
||||
// THEN keyguard session has a session id
|
||||
assertNotNull(mSessionTracker.getSessionId(SESSION_KEYGUARD));
|
||||
|
||||
// THEN send event to status bar service
|
||||
verify(mStatusBarService).onSessionStarted(eq(SESSION_KEYGUARD), any(InstanceId.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoSessions() throws RemoteException {
|
||||
// GIVEN no sessions
|
||||
when(mKeyguardStateController.isShowing()).thenReturn(false);
|
||||
|
||||
// WHEN started
|
||||
mSessionTracker.start();
|
||||
|
||||
// THEN all sessions are null
|
||||
for (int sessionType : ALL_SESSIONS) {
|
||||
assertNull(mSessionTracker.getSessionId(sessionType));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBiometricPromptShowing() throws RemoteException {
|
||||
// GIVEN session tracker started w/o any sessions
|
||||
mSessionTracker.start();
|
||||
captureAuthControllerCallback();
|
||||
|
||||
// WHEN auth controller shows the biometric prompt
|
||||
mAuthControllerCallback.onBiometricPromptShown();
|
||||
|
||||
// THEN the biometric prompt session has a session id
|
||||
assertNotNull(mSessionTracker.getSessionId(SESSION_BIOMETRIC_PROMPT));
|
||||
|
||||
// THEN session started event gets sent to status bar service
|
||||
verify(mStatusBarService).onSessionStarted(
|
||||
eq(SESSION_BIOMETRIC_PROMPT), any(InstanceId.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBiometricPromptDismissed() throws RemoteException {
|
||||
// GIVEN session tracker started w/o any sessions
|
||||
mSessionTracker.start();
|
||||
captureAuthControllerCallback();
|
||||
|
||||
// WHEN auth controller shows the biometric prompt and then hides it
|
||||
mAuthControllerCallback.onBiometricPromptShown();
|
||||
mAuthControllerCallback.onBiometricPromptDismissed();
|
||||
|
||||
// THEN the biometric prompt session no longer has a session id
|
||||
assertNull(mSessionTracker.getSessionId(SESSION_BIOMETRIC_PROMPT));
|
||||
|
||||
// THEN session end event gets sent to status bar service
|
||||
verify(mStatusBarService).onSessionEnded(
|
||||
eq(SESSION_BIOMETRIC_PROMPT), any(InstanceId.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyguardSessionOnDeviceStartsSleeping() throws RemoteException {
|
||||
// GIVEN session tracker started w/o any sessions
|
||||
mSessionTracker.start();
|
||||
captureKeyguardUpdateMonitorCallback();
|
||||
|
||||
// WHEN device starts going to sleep
|
||||
mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
|
||||
|
||||
// THEN the keyguard session has a session id
|
||||
assertNotNull(mSessionTracker.getSessionId(SESSION_KEYGUARD));
|
||||
|
||||
// THEN session start event gets sent to status bar service
|
||||
verify(mStatusBarService).onSessionStarted(
|
||||
eq(SESSION_KEYGUARD), any(InstanceId.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyguardSessionOnKeyguardShowingChange() throws RemoteException {
|
||||
// GIVEN session tracker started w/o any sessions
|
||||
mSessionTracker.start();
|
||||
captureKeyguardStateControllerCallback();
|
||||
|
||||
// WHEN keyguard becomes visible (ie: from lockdown)
|
||||
when(mKeyguardStateController.isShowing()).thenReturn(true);
|
||||
mKeyguardStateCallback.onKeyguardShowingChanged();
|
||||
|
||||
// THEN the keyguard session has a session id
|
||||
assertNotNull(mSessionTracker.getSessionId(SESSION_KEYGUARD));
|
||||
|
||||
// THEN session start event gets sent to status bar service
|
||||
verify(mStatusBarService).onSessionStarted(
|
||||
eq(SESSION_KEYGUARD), any(InstanceId.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyguardSessionOnKeyguardNotShowing() throws RemoteException {
|
||||
// GIVEN session tracker started w/o any sessions
|
||||
mSessionTracker.start();
|
||||
captureKeyguardStateControllerCallback();
|
||||
|
||||
// WHEN keyguard was showing and now it's not
|
||||
when(mKeyguardStateController.isShowing()).thenReturn(true);
|
||||
mKeyguardStateCallback.onKeyguardShowingChanged();
|
||||
when(mKeyguardStateController.isShowing()).thenReturn(false);
|
||||
mKeyguardStateCallback.onKeyguardShowingChanged();
|
||||
|
||||
// THEN the keyguard session no longer has a session id
|
||||
assertNull(mSessionTracker.getSessionId(SESSION_KEYGUARD));
|
||||
|
||||
// THEN session end event gets sent to status bar service
|
||||
verify(mStatusBarService).onSessionEnded(
|
||||
eq(SESSION_KEYGUARD), any(InstanceId.class));
|
||||
}
|
||||
|
||||
void captureKeyguardUpdateMonitorCallback() {
|
||||
verify(mKeyguardUpdateMonitor).registerCallback(
|
||||
mKeyguardUpdateMonitorCallbackCaptor.capture());
|
||||
mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
|
||||
}
|
||||
|
||||
void captureKeyguardStateControllerCallback() {
|
||||
verify(mKeyguardStateController).addCallback(
|
||||
mKeyguardStateCallbackCaptor.capture());
|
||||
mKeyguardStateCallback = mKeyguardStateCallbackCaptor.getValue();
|
||||
}
|
||||
|
||||
void captureAuthControllerCallback() {
|
||||
verify(mAuthController).addCallback(
|
||||
mAuthControllerCallbackCaptor.capture());
|
||||
mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Copyright (C) 2022 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.statusbar;
|
||||
|
||||
import static android.app.StatusBarManager.ALL_SESSIONS;
|
||||
import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT;
|
||||
import static android.app.StatusBarManager.SESSION_KEYGUARD;
|
||||
import static android.app.StatusBarManager.SessionFlags;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.logging.InstanceId;
|
||||
import com.android.internal.statusbar.ISessionListener;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Monitors session starts and ends. Session instanceIds can be used to correlate logs.
|
||||
*/
|
||||
public class SessionMonitor {
|
||||
private static final String TAG = "SessionMonitor";
|
||||
|
||||
private final Context mContext;
|
||||
private final Map<Integer, Set<ISessionListener>> mSessionToListeners =
|
||||
new HashMap<>();
|
||||
|
||||
/** */
|
||||
public SessionMonitor(Context context) {
|
||||
mContext = context;
|
||||
// initialize all sessions in the map
|
||||
for (int session : ALL_SESSIONS) {
|
||||
mSessionToListeners.put(session, new HashSet<>());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener for all sessionTypes included in sessionFlags.
|
||||
*/
|
||||
public void registerSessionListener(@SessionFlags int sessionFlags,
|
||||
ISessionListener listener) {
|
||||
requireListenerPermissions(sessionFlags);
|
||||
synchronized (mSessionToListeners) {
|
||||
for (int sessionType : ALL_SESSIONS) {
|
||||
if ((sessionFlags & sessionType) != 0) {
|
||||
mSessionToListeners.get(sessionType).add(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a listener for all sessionTypes included in sessionFlags.
|
||||
*/
|
||||
public void unregisterSessionListener(@SessionFlags int sessionFlags,
|
||||
ISessionListener listener) {
|
||||
synchronized (mSessionToListeners) {
|
||||
for (int sessionType : ALL_SESSIONS) {
|
||||
if ((sessionFlags & sessionType) != 0) {
|
||||
mSessionToListeners.get(sessionType).remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a session with the given sessionType, creating a new instanceId.
|
||||
* Sends this message to all listeners registered for the given sessionType.
|
||||
*
|
||||
* Callers require special permission to start and end a session depending on the session.
|
||||
*/
|
||||
public void onSessionStarted(@SessionFlags int sessionType, @NonNull InstanceId instanceId) {
|
||||
requireSetterPermissions(sessionType);
|
||||
|
||||
if (!isValidSessionType(sessionType)) {
|
||||
Log.e(TAG, "invalid onSessionStarted sessionType=" + sessionType);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (mSessionToListeners) {
|
||||
for (ISessionListener listener : mSessionToListeners.get(sessionType)) {
|
||||
try {
|
||||
listener.onSessionStarted(sessionType, instanceId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "unable to send session start to listener=" + listener, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends a session with the given sessionType and instanceId. Sends this message
|
||||
* to all listeners registered for the given sessionType.
|
||||
*
|
||||
* Callers require special permission to start and end a session depending on the session.
|
||||
*/
|
||||
public void onSessionEnded(@SessionFlags int sessionType, @NonNull InstanceId instanceId) {
|
||||
requireSetterPermissions(sessionType);
|
||||
|
||||
if (!isValidSessionType(sessionType)) {
|
||||
Log.e(TAG, "invalid onSessionEnded sessionType=" + sessionType);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (mSessionToListeners) {
|
||||
for (ISessionListener listener : mSessionToListeners.get(sessionType)) {
|
||||
try {
|
||||
listener.onSessionEnded(sessionType, instanceId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "unable to send session end to listener=" + listener, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidSessionType(@SessionFlags int sessionType) {
|
||||
return ALL_SESSIONS.contains(sessionType);
|
||||
}
|
||||
|
||||
private void requireListenerPermissions(@SessionFlags int sessionFlags) {
|
||||
if ((sessionFlags & SESSION_KEYGUARD) != 0) {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
Manifest.permission.MANAGE_BIOMETRIC,
|
||||
"StatusBarManagerService.SessionMonitor");
|
||||
}
|
||||
|
||||
if ((sessionFlags & SESSION_BIOMETRIC_PROMPT) != 0) {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
Manifest.permission.MANAGE_BIOMETRIC,
|
||||
"StatusBarManagerService.SessionMonitor");
|
||||
}
|
||||
}
|
||||
|
||||
private void requireSetterPermissions(@SessionFlags int sessionFlags) {
|
||||
if ((sessionFlags & SESSION_KEYGUARD) != 0) {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
Manifest.permission.CONTROL_KEYGUARD,
|
||||
"StatusBarManagerService.SessionMonitor");
|
||||
}
|
||||
|
||||
if ((sessionFlags & SESSION_BIOMETRIC_PROMPT) != 0) {
|
||||
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
|
||||
"StatusBarManagerService.SessionMonitor");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
|
||||
import static android.app.StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS;
|
||||
import static android.app.StatusBarManager.NAV_BAR_MODE_OVERRIDE_NONE;
|
||||
import static android.app.StatusBarManager.NavBarModeOverride;
|
||||
import static android.app.StatusBarManager.SessionFlags;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
|
||||
|
||||
@@ -84,8 +85,10 @@ import com.android.internal.R;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.inputmethod.SoftInputShowHideReason;
|
||||
import com.android.internal.logging.InstanceId;
|
||||
import com.android.internal.os.TransferPipe;
|
||||
import com.android.internal.statusbar.IAddTileResultCallback;
|
||||
import com.android.internal.statusbar.ISessionListener;
|
||||
import com.android.internal.statusbar.IStatusBar;
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.internal.statusbar.NotificationVisibility;
|
||||
@@ -145,6 +148,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
|
||||
private final ActivityManagerInternal mActivityManagerInternal;
|
||||
private final ActivityTaskManagerInternal mActivityTaskManager;
|
||||
private final PackageManagerInternal mPackageManagerInternal;
|
||||
private final SessionMonitor mSessionMonitor;
|
||||
private int mCurrentUserId;
|
||||
private boolean mTracingEnabled;
|
||||
|
||||
@@ -260,6 +264,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
|
||||
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
|
||||
|
||||
mTileRequestTracker = new TileRequestTracker(mContext);
|
||||
mSessionMonitor = new SessionMonitor(mContext);
|
||||
}
|
||||
|
||||
private IOverlayManager getOverlayManager() {
|
||||
@@ -1870,6 +1875,28 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionStarted(@SessionFlags int sessionType, InstanceId instance) {
|
||||
mSessionMonitor.onSessionStarted(sessionType, instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionEnded(@SessionFlags int sessionType, InstanceId instance) {
|
||||
mSessionMonitor.onSessionEnded(sessionType, instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerSessionListener(@SessionFlags int sessionFlags,
|
||||
ISessionListener listener) {
|
||||
mSessionMonitor.registerSessionListener(sessionFlags, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterSessionListener(@SessionFlags int sessionFlags,
|
||||
ISessionListener listener) {
|
||||
mSessionMonitor.unregisterSessionListener(sessionFlags, listener);
|
||||
}
|
||||
|
||||
public String[] getStatusBarIcons() {
|
||||
return mContext.getResources().getStringArray(R.array.config_statusBarIcons);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user