blob: f7b799c8addca592d9c2fceedd30c84143e61d77 [file] [log] [blame]
/*
* Copyright (C) 2020 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.google.android.connecteddevice.trust;
import static com.google.android.connecteddevice.util.SafeLog.logd;
import static com.google.android.connecteddevice.util.SafeLog.loge;
import static com.google.android.connecteddevice.util.SafeLog.logw;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.trust.TrustAgentService;
import androidx.annotation.VisibleForTesting;
import com.google.android.connecteddevice.trust.api.ITrustedDeviceAgentDelegate;
import com.google.android.connecteddevice.trust.api.ITrustedDeviceManager;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicBoolean;
/** Service to provide TrustAgent functionality to the trusted device feature. */
public class TrustedDeviceAgentService extends TrustAgentService {
private static final String TAG = "TrustedDeviceAgentService";
/** Keep device in trusted state. */
private static final long TRUST_DURATION_MS = 0L;
private static final String RETRY_HANDLER_THREAD_NAME = "trusted_device_retry";
private static final int MAX_RETRIES = 200;
private static final Duration RETRY_DURATION = Duration.ofMillis(10);
private final AtomicBoolean isManagingTrust = new AtomicBoolean(false);
private HandlerThread retryThread;
private Handler retryHandler;
@VisibleForTesting ITrustedDeviceManager trustedDeviceManager;
private int retries;
private UserManager userManager;
private KeyguardManager keyguardManager;
private PowerManager powerManager;
@SuppressLint("UnprotectedReceiver") // Broadcast is protected.
@Override
public void onCreate() {
super.onCreate();
logd(TAG, "Starting trust agent service.");
userManager = (UserManager) getSystemService(Context.USER_SERVICE);
keyguardManager = getSystemService(KeyguardManager.class);
powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
TrustedDeviceEventLog.onTrustAgentStarted();
registerReceiver(userUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
registerReceiver(screenOnReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON));
retryThread = new HandlerThread(RETRY_HANDLER_THREAD_NAME);
retryThread.start();
retryHandler = new Handler(retryThread.getLooper());
bindToService();
}
@Override
public void onDestroy() {
logd(TAG, "Destroying trust agent service.");
boolean isDeviceSecure = keyguardManager != null && keyguardManager.isDeviceSecure();
logd(TAG, "Device secure status: " + isDeviceSecure + ".");
if (trustedDeviceManager != null) {
try {
trustedDeviceManager.clearTrustedDeviceAgentDelegate(
trustedDeviceAgentDelegate,
isDeviceSecure);
} catch (RemoteException e) {
loge(TAG, "Error while disconnecting from TrustedDeviceManager.");
}
unbindService(serviceConnection);
}
if (retryThread != null) {
retryThread.quit();
}
unregisterReceiver(userUnlockedReceiver);
unregisterReceiver(screenOnReceiver);
super.onDestroy();
}
@Override
public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) {
super.onEscrowTokenAdded(token, handle, user);
if (trustedDeviceManager == null) {
loge(TAG, "Manager is null when escrow token was added. Ignoring.");
return;
}
try {
trustedDeviceManager.onEscrowTokenAdded(user.getIdentifier(), handle);
} catch (RemoteException e) {
loge(TAG, "Error while notifying that an escrow token was added.", e);
}
}
@Override
public void onEscrowTokenStateReceived(long handle, int tokenState) {
super.onEscrowTokenStateReceived(handle, tokenState);
if (trustedDeviceManager == null) {
loge(TAG, "Manager was null when escrow token was received. Ignoring.");
return;
}
if (tokenState == TrustAgentService.TOKEN_STATE_ACTIVE) {
try {
trustedDeviceManager.onEscrowTokenActivated(ActivityManager.getCurrentUser(), handle);
} catch (RemoteException e) {
loge(TAG, "Error while notifying that an escrow token was activated.", e);
}
}
}
@VisibleForTesting
void maybeDismissLockscreen() {
// Keyguard will ignore the dismissed request if the device is not in interactive mode, e.g.
// dozing or asleep.
if (!powerManager.isInteractive()) {
logw(TAG, "Screen is not on when try to dismiss lock screen, waiting for screen on.");
return;
}
if (!isManagingTrust.compareAndSet(true, false)) {
logd(TAG, "User was unlocked before receiving an escrow token.");
return;
}
logd(TAG, "Dismissing the lockscreen.");
grantTrust(
"Granting trust from escrow token for user.",
TRUST_DURATION_MS,
FLAG_GRANT_TRUST_DISMISS_KEYGUARD);
setManagingTrust(false);
if (trustedDeviceManager == null) {
loge(TAG, "Manager was null when device was unlocked. Ignoring.");
return;
}
// Other locking schemas, e.g. primary authentication, might keep the device locked even after
// granting trust.
if (keyguardManager == null || keyguardManager.isDeviceLocked()) {
logw(
TAG,
"Device is still locked after granting trust. Primary authentication may be enforced."
+ "Skip the ACK message to the phone.");
return;
}
TrustedDeviceEventLog.onUserUnlocked();
try {
trustedDeviceManager.onUserUnlocked();
} catch (RemoteException e) {
loge(TAG, "Error while notifying that the device was unlocked.", e);
}
}
@VisibleForTesting
boolean isUserUnlocked(int userId) {
return userManager.isUserUnlocked(UserHandle.of(userId));
}
@VisibleForTesting
void bindToService() {
Intent intent = new Intent(this, TrustedDeviceManagerService.class);
bindService(intent, serviceConnection, /* flags= */ 0);
}
@VisibleForTesting
final void setupManager() {
try {
trustedDeviceManager.setTrustedDeviceAgentDelegate(trustedDeviceAgentDelegate);
logd(TAG, "Successfully connected to TrustedDeviceManager.");
} catch (RemoteException e) {
loge(TAG, "Error while establishing connection to TrustedDeviceManager.", e);
}
}
private final ServiceConnection serviceConnection =
new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
trustedDeviceManager = ITrustedDeviceManager.Stub.asInterface(service);
setupManager();
}
@Override
public void onServiceDisconnected(ComponentName name) {
trustedDeviceManager = null;
bindToService();
}
};
private final ITrustedDeviceAgentDelegate trustedDeviceAgentDelegate =
new ITrustedDeviceAgentDelegate.Stub() {
@Override
public void addEscrowToken(byte[] token, int userId) {
logd(TAG, "Adding new escrow token for user " + userId + ".");
try {
TrustedDeviceAgentService.this.addEscrowToken(token, UserHandle.of(userId));
return;
} catch (IllegalStateException e) {
if (++retries > MAX_RETRIES) {
loge(
TAG,
"Maximum number of retries for adding an escrow token has "
+ "been exceeded. Aborting.",
e);
return;
}
}
logw(TAG, "Trust agent has not been bound to yet. Retry #" + retries + ".");
retryHandler.postDelayed(() -> addEscrowToken(token, userId), RETRY_DURATION.toMillis());
}
@Override
public void unlockUserWithToken(byte[] token, long handle, int userId) {
logd(TAG, "Received an escrow token for user " + userId + ".");
isManagingTrust.set(true);
setManagingTrust(true);
if (isUserUnlocked(userId)) {
logd(TAG, "User was already unlocked when token was received.");
maybeDismissLockscreen();
return;
}
logd(TAG, "Unlocking user with token.");
TrustedDeviceAgentService.this.unlockUserWithToken(handle, token, UserHandle.of(userId));
}
@Override
public void removeEscrowToken(long handle, int userId) {
logd(TAG, "Removing escrow token for user " + userId + ".");
TrustedDeviceAgentService.this.removeEscrowToken(handle, UserHandle.of(userId));
}
};
private final BroadcastReceiver userUnlockedReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
logd(TAG, "User unlocked; try to dismiss the lock screen.");
maybeDismissLockscreen();
}
};
/**
* Indicating the lock screen is ready to be dismissed.
*
* <p>Dismissing lock screen without screen on will fail silently.
*/
private final BroadcastReceiver screenOnReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
logd(TAG, "Screen on; device entered interactive mode.");
if (isUserUnlocked(ActivityManager.getCurrentUser())) {
logd(TAG, "User is already unlocked; dismiss the lock screen.");
// Make sure the user is unlocked before dismiss, otherwise the device will be in a
// strange state where the user storage is locked but the keyguard does not show up,
// which will probably leading to a black screen.
maybeDismissLockscreen();
}
}
};
}