blob: 21ade1bbbc4a8655fda54ee99ef28c51bb2037b3 [file] [log] [blame]
/*
* 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.biometrics.log;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.biometrics.AuthenticateOptions;
import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.common.OperationContext;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.Slog;
import android.view.Display;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.statusbar.ISessionListener;
import com.android.internal.statusbar.IStatusBarService;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
/**
* A default provider for {@link BiometricContext} that aggregates device state from SysUI
* and packages it into an {@link android.hardware.biometrics.common.OperationContext} that can
* be propagated to the HAL.
*/
public final class BiometricContextProvider implements BiometricContext {
private static final String TAG = "BiometricContextProvider";
private static final int SESSION_TYPES =
StatusBarManager.SESSION_KEYGUARD | StatusBarManager.SESSION_BIOMETRIC_PROMPT;
private static BiometricContextProvider sInstance;
static BiometricContextProvider defaultProvider(@NonNull Context context) {
synchronized (BiometricContextProvider.class) {
if (sInstance == null) {
try {
sInstance = new BiometricContextProvider(context,
(WindowManager) context.getSystemService(Context.WINDOW_SERVICE),
IStatusBarService.Stub.asInterface(ServiceManager.getServiceOrThrow(
Context.STATUS_BAR_SERVICE)), null /* handler */,
new AuthSessionCoordinator());
} catch (ServiceNotFoundException e) {
throw new IllegalStateException("Failed to find required service", e);
}
}
}
return sInstance;
}
@NonNull
private final Map<OperationContextExt, Consumer<OperationContext>> mSubscribers =
new ConcurrentHashMap<>();
@Nullable
private final Map<Integer, BiometricContextSessionInfo> mSession = new ConcurrentHashMap<>();
private final AuthSessionCoordinator mAuthSessionCoordinator;
private final WindowManager mWindowManager;
@Nullable private final Handler mHandler;
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mFoldState = IBiometricContextListener.FoldState.UNKNOWN;
private int mDisplayState = AuthenticateOptions.DISPLAY_STATE_UNKNOWN;
@VisibleForTesting
final BroadcastReceiver mDockStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mDockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
Intent.EXTRA_DOCK_STATE_UNDOCKED);
// no need to notify, not sent to HAL
}
};
@VisibleForTesting
public BiometricContextProvider(@NonNull Context context,
@NonNull WindowManager windowManager,
@NonNull IStatusBarService service, @Nullable Handler handler,
@NonNull AuthSessionCoordinator authSessionCoordinator) {
mWindowManager = windowManager;
mAuthSessionCoordinator = authSessionCoordinator;
mHandler = handler;
subscribeBiometricContextListener(service);
subscribeDockState(context);
}
private void subscribeBiometricContextListener(@NonNull IStatusBarService service) {
try {
service.setBiometicContextListener(new IBiometricContextListener.Stub() {
@Override
public void onFoldChanged(int foldState) {
mFoldState = foldState;
// no need to notify, not sent to HAL
}
@Override
public void onDisplayStateChanged(int displayState) {
if (displayState != mDisplayState) {
mDisplayState = displayState;
notifyChanged();
}
}
});
service.registerSessionListener(SESSION_TYPES, new ISessionListener.Stub() {
@Override
public void onSessionStarted(int sessionType, InstanceId instance) {
mSession.put(sessionType, new BiometricContextSessionInfo(instance));
}
@Override
public void onSessionEnded(int sessionType, InstanceId instance) {
final BiometricContextSessionInfo info = mSession.remove(sessionType);
if (info != null && instance != null && info.getId() != instance.getId()) {
Slog.w(TAG, "session id mismatch");
}
}
});
} catch (RemoteException e) {
Slog.e(TAG, "Unable to register biometric context listener", e);
}
}
private void subscribeDockState(@NonNull Context context) {
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_DOCK_EVENT);
context.registerReceiver(mDockStateReceiver, filter);
}
@Override
public OperationContextExt updateContext(@NonNull OperationContextExt operationContext,
boolean isCryptoOperation) {
return operationContext.update(this);
}
@Nullable
@Override
public BiometricContextSessionInfo getKeyguardEntrySessionInfo() {
return mSession.get(StatusBarManager.SESSION_KEYGUARD);
}
@Nullable
@Override
public BiometricContextSessionInfo getBiometricPromptSessionInfo() {
return mSession.get(StatusBarManager.SESSION_BIOMETRIC_PROMPT);
}
@Override
public boolean isAod() {
return mDisplayState == AuthenticateOptions.DISPLAY_STATE_AOD;
}
@Override
public boolean isAwake() {
switch (mDisplayState) {
case AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN:
case AuthenticateOptions.DISPLAY_STATE_SCREENSAVER:
case AuthenticateOptions.DISPLAY_STATE_UNKNOWN:
return true;
}
return false;
}
@Override
public boolean isDisplayOn() {
return mWindowManager.getDefaultDisplay().getState() == Display.STATE_ON;
}
@Override
public int getDockedState() {
return mDockState;
}
@Override
public int getFoldState() {
return mFoldState;
}
@Override
public int getCurrentRotation() {
return mWindowManager.getDefaultDisplay().getRotation();
}
@Override
public int getDisplayState() {
return mDisplayState;
}
@Override
public void subscribe(@NonNull OperationContextExt context,
@NonNull Consumer<OperationContext> consumer) {
mSubscribers.put(context, consumer);
}
@Override
public void unsubscribe(@NonNull OperationContextExt context) {
mSubscribers.remove(context);
}
@Override
public AuthSessionCoordinator getAuthSessionCoordinator() {
return mAuthSessionCoordinator;
}
private void notifyChanged() {
if (mHandler != null) {
mHandler.post(this::notifySubscribers);
} else {
notifySubscribers();
}
}
private void notifySubscribers() {
mSubscribers.forEach((context, consumer) -> {
consumer.accept(context.update(this).toAidlContext());
});
}
@Override
public String toString() {
return "[keyguard session: " + getKeyguardEntrySessionInfo() + ", "
+ "bp session: " + getBiometricPromptSessionInfo() + ", "
+ "displayState: " + getDisplayState() + ", "
+ "isAwake: " + isAwake() + ", "
+ "isDisplayOn: " + isDisplayOn() + ", "
+ "dock: " + getDockedState() + ", "
+ "rotation: " + getCurrentRotation() + "]";
}
}