blob: 1dfbd504a1f0f49ff4ca09a50841ce58d9cf77c6 [file] [log] [blame]
/*
* Copyright (C) 2016 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.car.trust;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.UserSwitchObserver;
import android.bluetooth.BluetoothAdapter;
import android.car.trust.ICarTrustAgentBleService;
import android.car.trust.ICarTrustAgentTokenRequestDelegate;
import android.car.trust.ICarTrustAgentUnlockCallback;
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.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.trust.TrustAgentService;
import android.util.Log;
/**
* A BluetoothLE (BLE) based {@link TrustAgentService} that uses the escrow token unlock APIs. </p>
*
* This trust agent runs during direct boot and binds to a BLE service that listens for remote
* devices to trigger an unlock. <p/>
*
* The permissions for this agent must be enabled as priv-app permissions for it to start.
*/
public class CarBleTrustAgent extends TrustAgentService {
private static final String TAG = CarBleTrustAgent.class.getSimpleName();
/**
* {@link CarTrustAgentBleService} will callback this function when it receives both
* handle and token.
*/
private final ICarTrustAgentUnlockCallback mUnlockCallback =
new ICarTrustAgentUnlockCallback.Stub() {
@Override
public void onUnlockDataReceived(byte[] token, long handle) throws RemoteException {
UserHandle userHandle = getUserHandleByTokenHandle(handle);
if (userHandle == null) {
Log.e(TAG, "Unable to find user by token handle " + handle);
return;
}
int uid = userHandle.getIdentifier();
if (ActivityManager.getCurrentUser() != uid) {
Log.d(TAG, "Switch to user: " + uid);
// Try to unlock when user switch completes
ActivityManager.getService().registerUserSwitchObserver(
getUserSwitchObserver(uid, token, handle), TAG);
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
am.switchUser(uid);
} else {
unlockUserInternally(uid, token, handle);
}
}
};
/**
* Delegates the escrow token API calls from {@link CarTrustAgentBleService} to
* {@link TrustAgentService}. Due to the asynchronous nature, the results will be posted to
* {@link CarTrustAgentBleService} by the following calls
* <ul>
* <li>{@link #onEscrowTokenAdded(byte[], long, UserHandle)}</li>
* <li>{@link #onEscrowTokenRemoved(long, boolean)}</li>
* <li>{@link #onEscrowTokenStateReceived(long, int)}</li>
* </ul>
*/
private final ICarTrustAgentTokenRequestDelegate mTokenRequestDelegate =
new ICarTrustAgentTokenRequestDelegate.Stub() {
@Override
public void revokeTrust() {
CarBleTrustAgent.this.revokeTrust();
}
@Override
public void addEscrowToken(byte[] token, int uid) {
CarBleTrustAgent.this.addEscrowToken(token, UserHandle.of(uid));
}
@Override
public void removeEscrowToken(long handle, int uid) {
CarBleTrustAgent.this.removeEscrowToken(handle, UserHandle.of(uid));
}
@Override
public void isEscrowTokenActive(long handle, int uid) {
CarBleTrustAgent.this.isEscrowTokenActive(handle, UserHandle.of(uid));
}
};
/**
* Service connection to {@link CarTrustAgentBleService}
*/
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "CarTrustAgentBleService connected");
mCarTrustAgentBleServiceBound = true;
mCarTrustAgentBleService = ICarTrustAgentBleService.Stub.asInterface(service);
try {
mCarTrustAgentBleService.registerUnlockCallback(mUnlockCallback);
mCarTrustAgentBleService.setTokenRequestDelegate(mTokenRequestDelegate);
} catch (RemoteException e) {
Log.e(TAG, "Error registerUnlockCallback", e);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "CarTrustAgentBleService disconnected");
if (mCarTrustAgentBleService != null) {
try {
mCarTrustAgentBleService.unregisterUnlockCallback(mUnlockCallback);
mCarTrustAgentBleService.setTokenRequestDelegate(null);
mCarTrustAgentBleService.stopUnlockAdvertising();
} catch (RemoteException e) {
Log.e(TAG, "Error unregisterUnlockCallback", e);
}
mCarTrustAgentBleService = null;
mCarTrustAgentBleServiceBound = false;
}
}
};
/**
* Receives the bluetooth state change broadcasts. Bluetooth is restarted when switching user,
* we need to ensure calling {@link ICarTrustAgentBleService#startUnlockAdvertising} after
* bluetooth is started.
*/
private final BroadcastReceiver mBluetoothBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case BluetoothAdapter.ACTION_STATE_CHANGED:
onBluetoothStateChanged(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1));
break;
}
}
};
private ICarTrustAgentBleService mCarTrustAgentBleService;
private boolean mCarTrustAgentBleServiceBound;
/**
* TODO: Currently it relies on {@link #onDeviceLocked()} and {@link #onDeviceUnlocked()}
* callback, and these callbacks won't happen if the user has unlocked once.
*/
private boolean mIsOnLockScreen;
@Override
public void onCreate() {
super.onCreate();
setManagingTrust(true);
bindService(new Intent(this, CarTrustAgentBleService.class),
mServiceConnection, Context.BIND_AUTO_CREATE);
IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothBroadcastReceiver, intentFilter);
}
@Override
public void onDestroy() {
Log.d(TAG, "Car Trust agent shutting down");
if (mCarTrustAgentBleServiceBound) {
unbindService(mServiceConnection);
}
unregisterReceiver(mBluetoothBroadcastReceiver);
super.onDestroy();
}
@Override
public void onDeviceLocked() {
super.onDeviceLocked();
mIsOnLockScreen = true;
if (mCarTrustAgentBleServiceBound) {
try {
mCarTrustAgentBleService.startUnlockAdvertising();
} catch (RemoteException e) {
Log.e(TAG, "Error startUnlockAdvertising", e);
}
}
}
@Override
public void onDeviceUnlocked() {
super.onDeviceUnlocked();
mIsOnLockScreen = false;
if (mCarTrustAgentBleServiceBound) {
try {
mCarTrustAgentBleService.stopUnlockAdvertising();
} catch (RemoteException e) {
Log.e(TAG, "Error stopUnlockAdvertising", e);
}
}
// Revoke trust right after to enable keyguard when switching user
revokeTrust();
}
private UserSwitchObserver getUserSwitchObserver(int uid,
byte[] token, long handle) {
return new UserSwitchObserver() {
@Override
public void onUserSwitchComplete(int newUserId) throws RemoteException {
if (uid != newUserId) return;
unlockUserInternally(uid, token, handle);
ActivityManager.getService().unregisterUserSwitchObserver(this);
}
@Override
public void onLockedBootComplete(int newUserId) {
// ignored.
}
};
}
private void unlockUserInternally(int uid, byte[] token, long handle) {
Log.d(TAG, "About to unlock user: " + uid);
unlockUserWithToken(handle, token, UserHandle.of(uid));
grantTrust("Granting trust from escrow token",
0, FLAG_GRANT_TRUST_DISMISS_KEYGUARD);
}
private void onBluetoothStateChanged(int state) {
Log.d(TAG, "onBluetoothStateChanged: " + state);
if ((state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_BLE_ON)
&& mCarTrustAgentBleServiceBound
&& mIsOnLockScreen) {
try {
mCarTrustAgentBleService.startUnlockAdvertising();
} catch (RemoteException e) {
Log.e(TAG, "Error startUnlockAdvertising", e);
}
}
}
@Override
public void onEscrowTokenRemoved(long handle, boolean successful) {
if (mCarTrustAgentBleServiceBound) {
try {
mCarTrustAgentBleService.onEscrowTokenRemoved(handle, successful);
Log.v(TAG, "Callback onEscrowTokenRemoved");
} catch (RemoteException e) {
Log.e(TAG, "Error callback onEscrowTokenRemoved", e);
}
}
}
@Override
public void onEscrowTokenStateReceived(long handle, int tokenState) {
boolean isActive = tokenState == TOKEN_STATE_ACTIVE;
if (mCarTrustAgentBleServiceBound) {
try {
mCarTrustAgentBleService.onEscrowTokenActiveStateChanged(handle, isActive);
Log.v(TAG, "Callback onEscrowTokenActiveStateChanged");
} catch (RemoteException e) {
Log.e(TAG, "Error callback onEscrowTokenActiveStateChanged", e);
}
}
}
@Override
public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) {
if (mCarTrustAgentBleServiceBound) {
try {
mCarTrustAgentBleService.onEscrowTokenAdded(token, handle, user.getIdentifier());
Log.v(TAG, "Callback onEscrowTokenAdded");
} catch (RemoteException e) {
Log.e(TAG, "Error callback onEscrowTokenAdded", e);
}
}
}
private @Nullable UserHandle getUserHandleByTokenHandle(long tokenHandle) {
if (mCarTrustAgentBleServiceBound) {
try {
int userId = mCarTrustAgentBleService.getUserIdByEscrowTokenHandle(tokenHandle);
return userId < 0 ? null : UserHandle.of(userId);
} catch (RemoteException e) {
Log.e(TAG, "Error getUserHandleByTokenHandle");
}
}
return null;
}
}